Drupal: ověřování uživatelů.
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed Oct 19 21:40:51 2011 +0200 (2011-10-19)
changeset 101d54786065fa3
parent 100 08c9fb6fb017
child 102 d843b4fee5dc
Drupal: ověřování uživatelů.
helpers/commands.list
src/org/sonews/acl/DrupalAuthInfoCommand.java
src/org/sonews/daemon/NNTPConnection.java
src/org/sonews/daemon/command/PostCommand.java
src/org/sonews/storage/Article.java
src/org/sonews/storage/Storage.java
src/org/sonews/storage/impl/DrupalDatabase.java
src/org/sonews/storage/impl/JDBCDatabase.java
     1.1 --- a/helpers/commands.list	Wed Oct 19 17:23:53 2011 +0200
     1.2 +++ b/helpers/commands.list	Wed Oct 19 21:40:51 2011 +0200
     1.3 @@ -12,4 +12,5 @@
     1.4  org.sonews.daemon.command.QuitCommand
     1.5  org.sonews.daemon.command.StatCommand
     1.6  org.sonews.daemon.command.XDaemonCommand
     1.7 -org.sonews.daemon.command.XPatCommand
     1.8 \ No newline at end of file
     1.9 +org.sonews.daemon.command.XPatCommand
    1.10 +org.sonews.acl.DrupalAuthInfoCommand
    1.11 \ No newline at end of file
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/org/sonews/acl/DrupalAuthInfoCommand.java	Wed Oct 19 21:40:51 2011 +0200
     2.3 @@ -0,0 +1,107 @@
     2.4 +/*
     2.5 + *   SONEWS News Server
     2.6 + *   see AUTHORS for the list of contributors
     2.7 + *
     2.8 + *   This program is free software: you can redistribute it and/or modify
     2.9 + *   it under the terms of the GNU General Public License as published by
    2.10 + *   the Free Software Foundation, either version 3 of the License, or
    2.11 + *   (at your option) any later version.
    2.12 + *
    2.13 + *   This program is distributed in the hope that it will be useful,
    2.14 + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.15 + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.16 + *   GNU General Public License for more details.
    2.17 + *
    2.18 + *   You should have received a copy of the GNU General Public License
    2.19 + *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    2.20 + */
    2.21 +package org.sonews.acl;
    2.22 +
    2.23 +import java.io.IOException;
    2.24 +import java.util.Arrays;
    2.25 +import java.util.logging.Level;
    2.26 +import java.util.logging.Logger;
    2.27 +import java.util.regex.Matcher;
    2.28 +import java.util.regex.Pattern;
    2.29 +import org.sonews.daemon.NNTPConnection;
    2.30 +import org.sonews.daemon.command.Command;
    2.31 +import org.sonews.storage.StorageBackendException;
    2.32 +import org.sonews.storage.StorageManager;
    2.33 +import org.sonews.storage.StorageProvider;
    2.34 +import org.sonews.storage.impl.DrupalDatabaseProvider;
    2.35 +
    2.36 +/**
    2.37 + *
    2.38 + * @author František Kučera (frantovo.cz)
    2.39 + */
    2.40 +public class DrupalAuthInfoCommand implements Command {
    2.41 +
    2.42 +	private static final Logger log = Logger.getLogger(DrupalAuthInfoCommand.class.getName());
    2.43 +	private static String[] SUPPORTED_COMMANDS = {"AUTHINFO"};
    2.44 +
    2.45 +	@Override
    2.46 +	public boolean hasFinished() {
    2.47 +		return true;
    2.48 +	}
    2.49 +
    2.50 +	@Override
    2.51 +	public String impliedCapability() {
    2.52 +		return "AUTHINFO";
    2.53 +	}
    2.54 +
    2.55 +	@Override
    2.56 +	public boolean isStateful() {
    2.57 +		return false;
    2.58 +	}
    2.59 +
    2.60 +	@Override
    2.61 +	public String[] getSupportedCommandStrings() {
    2.62 +		return SUPPORTED_COMMANDS;
    2.63 +	}
    2.64 +
    2.65 +	@Override
    2.66 +	public void processLine(NNTPConnection conn, String line, byte[] rawLine) throws IOException, StorageBackendException {
    2.67 +		Pattern commandPattern = Pattern.compile("AUTHINFO (USER|PASS) (.*)", Pattern.CASE_INSENSITIVE);
    2.68 +		Matcher commandMatcher = commandPattern.matcher(line);
    2.69 +
    2.70 +		if (commandMatcher.matches()) {
    2.71 +
    2.72 +			if (conn.isUserAuthenticated()) {
    2.73 +				conn.println("502 Command unavailable (you are already authenticated)");
    2.74 +			} else if ("USER".equalsIgnoreCase(commandMatcher.group(1))) {
    2.75 +				conn.setUsername(commandMatcher.group(2));
    2.76 +				conn.println("381 Password required");
    2.77 +				log.log(Level.FINE, "User ''{0}'' greets us. We are waiting for his password.", conn.getUsername());
    2.78 +			} else if ("PASS".equalsIgnoreCase(commandMatcher.group(1))) {
    2.79 +				if (conn.getUsername() == null) {
    2.80 +					conn.println("482 Authentication commands issued out of sequence");
    2.81 +				} else {
    2.82 +
    2.83 +					char[] password = commandMatcher.group(2).toCharArray();
    2.84 +					boolean goodPassword = StorageManager.current().authenticateUser(conn.getUsername(), password);
    2.85 +					Arrays.fill(password, '*');
    2.86 +					commandMatcher = null;
    2.87 +
    2.88 +					if (goodPassword) {
    2.89 +						conn.println("281 Authentication accepted");
    2.90 +						conn.setUserAuthenticated(true);
    2.91 +						log.log(Level.INFO, "User ''{0}'' has been succesfully authenticated.", conn.getUsername());
    2.92 +					} else {
    2.93 +						log.log(Level.INFO, "User ''{0}'' has provided wrong password.", conn.getUsername());
    2.94 +						conn.setUsername(null);
    2.95 +						conn.setUserAuthenticated(false);
    2.96 +						conn.println("481 Authentication failed: wrong password");
    2.97 +					}
    2.98 +
    2.99 +				}
   2.100 +			} else {
   2.101 +				// impossible, see commandPattern
   2.102 +				conn.println("500 Unknown command");
   2.103 +			}
   2.104 +
   2.105 +
   2.106 +		} else {
   2.107 +			conn.println("500 Unknown command, expecting AUTHINFO USER username or AUTHINFO PASS password ");
   2.108 +		}
   2.109 +	}
   2.110 +}
     3.1 --- a/src/org/sonews/daemon/NNTPConnection.java	Wed Oct 19 17:23:53 2011 +0200
     3.2 +++ b/src/org/sonews/daemon/NNTPConnection.java	Wed Oct 19 21:40:51 2011 +0200
     3.3 @@ -59,6 +59,9 @@
     3.4  	private int readLock = 0;
     3.5  	private final Object readLockGate = new Object();
     3.6  	private SelectionKey writeSelKey = null;
     3.7 +	
     3.8 +	private String username;
     3.9 +	private boolean userAuthenticated = false;
    3.10  
    3.11  	public NNTPConnection(final SocketChannel channel)
    3.12  			throws IOException {
    3.13 @@ -360,4 +363,36 @@
    3.14  	void setLastActivity(long timestamp) {
    3.15  		this.lastActivity = timestamp;
    3.16  	}
    3.17 +
    3.18 +	/**
    3.19 +	 * @return Current username. 
    3.20 +	 * But user may not have been authenticated yet.
    3.21 +	 * You must check {@link #isUserAuthenticated()}
    3.22 +	 */
    3.23 +	public String getUsername() {
    3.24 +		return username;
    3.25 +	}
    3.26 +
    3.27 +	/**
    3.28 +	 * This method is to be called from AUTHINFO USER Command implementation.
    3.29 +	 * @param username username from AUTHINFO USER username.
    3.30 +	 */
    3.31 +	public void setUsername(String username) {
    3.32 +		this.username = username;
    3.33 +	}
    3.34 +
    3.35 +	/**
    3.36 +	 * @return true if current user (see {@link #getUsername()}) has been succesfully authenticated.
    3.37 +	 */
    3.38 +	public boolean isUserAuthenticated() {
    3.39 +		return userAuthenticated;
    3.40 +	}
    3.41 +
    3.42 +	/**
    3.43 +	 * This method is to be called from AUTHINFO PASS Command implementation.
    3.44 +	 * @param userAuthenticated true if user has provided right password in AUTHINFO PASS password.
    3.45 +	 */
    3.46 +	public void setUserAuthenticated(boolean userAuthenticated) {
    3.47 +		this.userAuthenticated = userAuthenticated;
    3.48 +	}
    3.49  }
     4.1 --- a/src/org/sonews/daemon/command/PostCommand.java	Wed Oct 19 17:23:53 2011 +0200
     4.2 +++ b/src/org/sonews/daemon/command/PostCommand.java	Wed Oct 19 21:40:51 2011 +0200
     4.3 @@ -210,6 +210,10 @@
     4.4  
     4.5  	private void postArticle(NNTPConnection conn, Article article)
     4.6  			throws IOException {
     4.7 +		if (conn.isUserAuthenticated()) {
     4.8 +			article.setAuthenticatedUser(conn.getUsername());
     4.9 +		}
    4.10 +		
    4.11  		if (article.getHeader(Headers.CONTROL)[0].length() > 0) {
    4.12  			controlMessage(conn, article);
    4.13  		} else if (article.getHeader(Headers.SUPERSEDES)[0].length() > 0) {
    4.14 @@ -218,7 +222,7 @@
    4.15  			// Circle check; note that Path can already contain the hostname here
    4.16  			String host = Config.inst().get(Config.HOSTNAME, "localhost");
    4.17  			if (article.getHeader(Headers.PATH)[0].indexOf(host + "!", 1) > 0) {
    4.18 -				Log.get().info(article.getMessageID() + " skipped for host " + host);
    4.19 +				Log.get().log(Level.INFO, "{0} skipped for host {1}", new Object[]{article.getMessageID(), host});
    4.20  				conn.println("441 I know this article already");
    4.21  				return;
    4.22  			}
     5.1 --- a/src/org/sonews/storage/Article.java	Wed Oct 19 17:23:53 2011 +0200
     5.2 +++ b/src/org/sonews/storage/Article.java	Wed Oct 19 21:40:51 2011 +0200
     5.3 @@ -41,6 +41,8 @@
     5.4   * @since n3tpd/0.1
     5.5   */
     5.6  public class Article extends ArticleHead {
     5.7 +	
     5.8 +	private String authenticatedUser;
     5.9  
    5.10  	/**
    5.11  	 * Loads the Article identified by the given ID from the JDBCDatabase.
    5.12 @@ -220,4 +222,19 @@
    5.13  	public String toString() {
    5.14  		return getMessageID();
    5.15  	}
    5.16 +
    5.17 +	/**
    5.18 +	 * @return username of currently logged user – or null, if user is not authenticated.
    5.19 +	 */
    5.20 +	public String getAuthenticatedUser() {
    5.21 +		return authenticatedUser;
    5.22 +	}
    5.23 +	
    5.24 +	/**
    5.25 +	 * This method is to be called from POST Command implementation.
    5.26 +	 * @param authenticatedUser current username – or null, if user is not authenticated.
    5.27 +	 */
    5.28 +	public void setAuthenticatedUser(String authenticatedUser) {
    5.29 +		this.authenticatedUser = authenticatedUser;
    5.30 +	}
    5.31  }
     6.1 --- a/src/org/sonews/storage/Storage.java	Wed Oct 19 17:23:53 2011 +0200
     6.2 +++ b/src/org/sonews/storage/Storage.java	Wed Oct 19 21:40:51 2011 +0200
     6.3 @@ -144,4 +144,7 @@
     6.4  
     6.5  	boolean update(Group group)
     6.6  			throws StorageBackendException;
     6.7 +
     6.8 +	public boolean authenticateUser(String username, char[] password)
     6.9 +			throws StorageBackendException;
    6.10  }
     7.1 --- a/src/org/sonews/storage/impl/DrupalDatabase.java	Wed Oct 19 17:23:53 2011 +0200
     7.2 +++ b/src/org/sonews/storage/impl/DrupalDatabase.java	Wed Oct 19 21:40:51 2011 +0200
     7.3 @@ -28,6 +28,7 @@
     7.4  import java.util.logging.Level;
     7.5  import java.util.logging.Logger;
     7.6  import org.sonews.config.Config;
     7.7 +import org.sonews.daemon.Connections;
     7.8  import org.sonews.feed.Subscription;
     7.9  import org.sonews.storage.Article;
    7.10  import org.sonews.storage.ArticleHead;
    7.11 @@ -387,9 +388,41 @@
    7.12  		return Collections.emptyList();
    7.13  	}
    7.14  
    7.15 +	/**
    7.16 +	 * Checks username and password.
    7.17 +	 * @param username
    7.18 +	 * @param password
    7.19 +	 * @return true if credentials are valid | false otherwise
    7.20 +	 * @throws StorageBackendException it there is any error during authentication process 
    7.21 +	 * (but should not be thrown if only bad thing is wrong username or password)
    7.22 +	 */
    7.23 +	@Override
    7.24 +	public boolean authenticateUser(String username, char[] password) throws StorageBackendException {
    7.25 +		PreparedStatement ps = null;
    7.26 +		ResultSet rs = null;
    7.27 +		try {
    7.28 +			ps = conn.prepareStatement("SELECT nntp_login(?, ?)");
    7.29 +			ps.setString(1, username);
    7.30 +			ps.setString(2, String.copyValueOf(password));
    7.31 +			rs = ps.executeQuery();
    7.32 +			rs.next();
    7.33 +			return rs.getInt(1) == 1;
    7.34 +		} catch (Exception e) {
    7.35 +			throw new StorageBackendException(e);
    7.36 +		} finally {
    7.37 +			close(null, ps, rs);
    7.38 +		}
    7.39 +	}
    7.40 +
    7.41  	@Override
    7.42  	public void addArticle(Article art) throws StorageBackendException {
    7.43 -		log.log(Level.SEVERE, "TODO: addArticle {0}", new Object[]{art});
    7.44 +		if (art.getAuthenticatedUser() == null) {
    7.45 +			log.log(Level.SEVERE, "User was not authenticated, so his article was rejected.");
    7.46 +			throw new StorageBackendException("User must be authenticated to post articles");
    7.47 +		} else {
    7.48 +
    7.49 +			log.log(Level.INFO, "User ''{0}'' has posted an article", art.getAuthenticatedUser());
    7.50 +		}
    7.51  	}
    7.52  
    7.53  	@Override
     8.1 --- a/src/org/sonews/storage/impl/JDBCDatabase.java	Wed Oct 19 17:23:53 2011 +0200
     8.2 +++ b/src/org/sonews/storage/impl/JDBCDatabase.java	Wed Oct 19 21:40:51 2011 +0200
     8.3 @@ -1468,4 +1468,10 @@
     8.4  			return update(group);
     8.5  		}
     8.6  	}
     8.7 +
     8.8 +	@Override
     8.9 +	public boolean authenticateUser(String username, char[] password) 
    8.10 +			throws StorageBackendException {
    8.11 +		throw new StorageBackendException("Not supported yet.");
    8.12 +	}
    8.13  }