|
| 1 | +/* |
| 2 | + * Copyright (C) 2000 - 2015 Silverpeas |
| 3 | + * |
| 4 | + * This program is free software: you can redistribute it and/or modify it under the terms of the |
| 5 | + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 |
| 6 | + * of the License, or (at your option) any later version. |
| 7 | + * |
| 8 | + * As a special exception to the terms and conditions of version 3.0 of the GPL, you may |
| 9 | + * redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS") |
| 10 | + * applications as described in Silverpeas's FLOSS exception. You should have received a copy of the |
| 11 | + * text describing the FLOSS exception, and it is also available here: |
| 12 | + * "http://www.silverpeas.org/docs/core/legal/floss_exception.html" |
| 13 | + * |
| 14 | + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without |
| 15 | + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | + * Affero General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU Affero General Public License along with this program. |
| 19 | + * If not, see <http://www.gnu.org/licenses/>. |
| 20 | + */ |
| 21 | + |
| 22 | +package org.silverpeas.jcr.auth; |
| 23 | + |
| 24 | +import org.silverpeas.jcr.auth.encryption.PasswordEncryption; |
| 25 | +import org.silverpeas.jcr.auth.encryption.PasswordEncryptionFactory; |
| 26 | + |
| 27 | +import javax.jcr.Credentials; |
| 28 | +import javax.jcr.SimpleCredentials; |
| 29 | +import java.security.Principal; |
| 30 | +import java.sql.Connection; |
| 31 | +import java.sql.PreparedStatement; |
| 32 | +import java.sql.ResultSet; |
| 33 | +import java.sql.SQLException; |
| 34 | +import java.text.MessageFormat; |
| 35 | +import java.util.logging.Level; |
| 36 | +import java.util.logging.Logger; |
| 37 | + |
| 38 | +/** |
| 39 | + * A simple authentication by login/password. It accesses the Silverpeas database to fetch the |
| 40 | + * encrypted password of the user in order to validate the one passing in the credentials. For |
| 41 | + * doing, the domain to which the user belongs has to be backed into a the Silverpeas database and |
| 42 | + * then it doesn't support domains backed by a LDAP directory or any data sources other than |
| 43 | + * the Silverpeas database. |
| 44 | + * This mechanism is mainly used to access JCR outside the context of Silverpeas; for example by |
| 45 | + * using an external service (like Crash). For a better authentication mechanism, we strongly |
| 46 | + * recommend to use the one based upon the volatile security tokens. |
| 47 | + * @author mmoquillon |
| 48 | + */ |
| 49 | +public class SQLSimpleAuthentication extends AbstractAuthentication { |
| 50 | + |
| 51 | + private static final String SELECT_DOMAIN_TABLE = |
| 52 | + "select propfilename, classname from st_domain where id = ?"; |
| 53 | + |
| 54 | + private static final String SELECT_USER_DATA = |
| 55 | + "select du.id, du.password, u.accesslevel from {0}_user du left join st_user u on du.id = " + |
| 56 | + "u.id where du.login = ? and u.state = 'VALID'"; |
| 57 | + |
| 58 | + /** |
| 59 | + * Authenticates a user by its credentials. |
| 60 | + * @param credentials the simple credentials of a user. If the credentials aren't with the |
| 61 | + * expected type, then null is returned. |
| 62 | + * @return the principal of the authenticated user or null if the credentials are not supported |
| 63 | + * by this authentication mechanism. |
| 64 | + * @throws AuthenticationException if the authentication fails. |
| 65 | + */ |
| 66 | + @Override |
| 67 | + public Principal authenticate(final Credentials credentials) throws AuthenticationException { |
| 68 | + Principal principal = null; |
| 69 | + if (credentials instanceof SimpleCredentials) { |
| 70 | + principal = authenticate((SimpleCredentials) credentials); |
| 71 | + } |
| 72 | + return principal; |
| 73 | + } |
| 74 | + |
| 75 | + /** |
| 76 | + * Authenticates a user with the specified simple credentials. |
| 77 | + * <p> |
| 78 | + * The authentication mechanism expects the user identifier in the credentials is in the form of |
| 79 | + * USER_LOGIN'@'SILVERPEAS_DOMAIN where USER_LOGIN is the login of the user to sign in Silverpeas |
| 80 | + * and the SILVERPEAS_DOMAIN is the identifier of the domain in Silverpeas to which the user |
| 81 | + * belongs. |
| 82 | + * </p> |
| 83 | + * @param credentials the simple credentials of a user. |
| 84 | + * @return the principal of the user or null if either the credentials of the user aren't taken |
| 85 | + * into account by this authentication mechanism or the credentials of the user aren't full |
| 86 | + * stored |
| 87 | + * into the data source of Silverpeas. |
| 88 | + * @throws AuthenticationException if the authentication fails (the pair identifier/password |
| 89 | + * isn't valid or no user matches the login and the domain specified in the credentials). |
| 90 | + */ |
| 91 | + public Principal authenticate(SimpleCredentials credentials) throws AuthenticationException { |
| 92 | + Principal principal = null; |
| 93 | + String[] userIdParts = fetchUserIdParts(credentials.getUserID()); |
| 94 | + if (userIdParts != null && userIdParts.length == 2) { |
| 95 | + String login = userIdParts[0]; |
| 96 | + String domainId = userIdParts[1]; |
| 97 | + SilverpeasUser user = getSilverpeasUserByDomain(login, domainId); |
| 98 | + if (user != null) { |
| 99 | + if (user.mustBeAuthenticated()) { |
| 100 | + PasswordEncryption encryption = PasswordEncryptionFactory.getFactory() |
| 101 | + .getPasswordEncryption(user.getEncryptedPassword()); |
| 102 | + try { |
| 103 | + encryption.check(new String(credentials.getPassword()), user.getEncryptedPassword()); |
| 104 | + } catch (AssertionError error) { |
| 105 | + throw new AuthenticationException(error.getMessage()); |
| 106 | + } |
| 107 | + } |
| 108 | + principal = getSilverpeasUserPrincipal(user); |
| 109 | + } else { |
| 110 | + throw new AuthenticationException("No user matching the login " + login + |
| 111 | + " and the domain identifier " + domainId); |
| 112 | + } |
| 113 | + } |
| 114 | + return principal; |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Gets information about the user matching the specified login for the specified domain |
| 119 | + * identifier. The domain should be backed by a database as the user is first identified within |
| 120 | + * its domain before getting any information about him. Among the information, there is the |
| 121 | + * encrypted password with which the authentication can be performed. |
| 122 | + * @param login the user login. |
| 123 | + * @param domainId the unique identifier of the domain to which the user belongs. |
| 124 | + * @return the user matching the specified login and domain or null. |
| 125 | + */ |
| 126 | + private SilverpeasUser getSilverpeasUserByDomain(final String login, final String domainId) { |
| 127 | + SilverpeasUser user = SilverpeasUser.asJcrSystemUser(); |
| 128 | + if (!user.getLogin().equals(login) || !user.getDomainId().equals(domainId)) { |
| 129 | + user = null; |
| 130 | + try (Connection connection = openConnectionToDataSource(); |
| 131 | + PreparedStatement domainStatement = connection.prepareStatement(SELECT_DOMAIN_TABLE)) { |
| 132 | + domainStatement.setInt(1, Integer.parseInt(domainId)); |
| 133 | + try (ResultSet domainResultSet = domainStatement.executeQuery()) { |
| 134 | + if (domainResultSet.next()) { |
| 135 | + String className = domainResultSet.getString("classname"); |
| 136 | + if (className != null && (className.toLowerCase().endsWith("sqldriver") || |
| 137 | + className.toLowerCase().endsWith("silverpeasdomaindriver"))) { |
| 138 | + String domainName = fetchDomainNameFrom(domainResultSet.getString("propfilename")); |
| 139 | + String sqlUserData = computeUserDataSQLQueryFor(domainName); |
| 140 | + try (PreparedStatement userStatement = connection.prepareStatement(sqlUserData)) { |
| 141 | + userStatement.setString(1, login); |
| 142 | + try (ResultSet userResultSet = userStatement.executeQuery()) { |
| 143 | + if (userResultSet.next()) { |
| 144 | + user = new SilverpeasUser().withId(userResultSet.getString("id")) |
| 145 | + .withDomainId(domainId) |
| 146 | + .withAccessLevel(userResultSet.getString("accesslevel")) |
| 147 | + .withEncryptedPassword(userResultSet.getString("password")); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + } catch (SQLException e) { |
| 155 | + Logger.getLogger(getClass().getSimpleName()).log(Level.SEVERE, e.getMessage(), e); |
| 156 | + } |
| 157 | + } |
| 158 | + return user; |
| 159 | + } |
| 160 | + |
| 161 | + private String fetchDomainNameFrom(String propFileName) { |
| 162 | + int lastSepIndex = propFileName.lastIndexOf("."); |
| 163 | + return (lastSepIndex > 0 ? propFileName.substring(lastSepIndex + 1).toLowerCase() : null); |
| 164 | + } |
| 165 | + |
| 166 | + private String computeUserDataSQLQueryFor(String tableName) { |
| 167 | + return MessageFormat.format(SELECT_USER_DATA, tableName); |
| 168 | + } |
| 169 | +} |
0 commit comments