Drupal: ověřování uživatelů.
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 }