Skip to content

Commit bd2d33e

Browse files
committed
Refactor the way the authentication by security tokens is performed. Rewrite the access control management for the WebDAV access of documents
1 parent 8d7699b commit bd2d33e

9 files changed

+740
-445
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.jaas.SilverpeasJcrSystemPrincipal;
25+
import org.silverpeas.jcr.jaas.SilverpeasUserPrincipal;
26+
import org.silverpeas.jcr.jaas.SilverpeasUserProfile;
27+
28+
import javax.naming.InitialContext;
29+
import javax.naming.NamingException;
30+
import javax.sql.DataSource;
31+
import java.security.Principal;
32+
import java.sql.Connection;
33+
import java.sql.PreparedStatement;
34+
import java.sql.ResultSet;
35+
import java.sql.SQLException;
36+
import java.text.MessageFormat;
37+
import java.util.logging.Level;
38+
import java.util.logging.Logger;
39+
40+
/**
41+
* This class defines common operations authentication mechanisms can require in order to perform
42+
* their authentication.
43+
* @author mmoquillon
44+
*/
45+
public abstract class AbstractAuthentication implements Authentication {
46+
47+
private static final String SELECT_USER_ROLES =
48+
"select r.rolename, r.instanceid, c.componentname from st_userrole_user_rel u join " +
49+
"st_userrole r on u.userroleid = r.id join st_component c on c.id = r.instanceid where " +
50+
"u.userid = ?";
51+
52+
protected Principal getSilverpeasUserPrincipal(final SilverpeasUser user) {
53+
Principal principal;
54+
if (user.isJcrSystemUser()) {
55+
principal = new SilverpeasJcrSystemPrincipal();
56+
} else {
57+
try (Connection connection = openConnectionToDataSource();
58+
PreparedStatement statement = connection.prepareStatement(SELECT_USER_ROLES)) {
59+
statement.setInt(1, Integer.parseInt(user.getId()));
60+
try (ResultSet resultSet = statement.executeQuery()) {
61+
principal = new SilverpeasUserPrincipal(user.getId(), "A".equals(user.getAccessLevel()));
62+
while (resultSet.next()) {
63+
String componentInstanceId = resultSet.getString("instanceid");
64+
String componentName = resultSet.getString("componentname");
65+
String roleName = resultSet.getString("rolename");
66+
SilverpeasUserProfile profile =
67+
new SilverpeasUserProfile(componentName + componentInstanceId, roleName);
68+
((SilverpeasUserPrincipal) principal).addUserProfile(profile);
69+
}
70+
}
71+
} catch (SQLException e) {
72+
principal = null;
73+
Logger.getLogger(getClass().getSimpleName()).log(Level.SEVERE, e.getMessage(), e);
74+
}
75+
}
76+
return principal;
77+
}
78+
79+
protected String[] fetchUserIdParts(String userId) {
80+
return userId.split("@domain");
81+
}
82+
83+
protected Connection openConnectionToDataSource() throws SQLException {
84+
try {
85+
DataSource dataSource = InitialContext.doLookup("java:/datasources/silverpeas");
86+
return dataSource.getConnection();
87+
} catch (NamingException e) {
88+
throw new SQLException(e);
89+
}
90+
}
91+
}

src/main/java/org/silverpeas/jcr/auth/Authentication.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public interface Authentication {
3434
/**
3535
* Authenticates a user by its credentials.
3636
* @param credentials the credentials of a user.
37-
* @return the principal of the authenticated user.
37+
* @return the principal of the authenticated user or null if the specified credentials aren't
38+
* supported by this authentication.
3839
* @throws AuthenticationException if the authentication fails.
3940
*/
4041
public Principal authenticate(final Credentials credentials) throws AuthenticationException;
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
package org.silverpeas.jcr.auth;
22

33
/**
4-
* A provider of an implementation of the {@code org.silverpeas.jcr.auth.Authentication} interface.
4+
* A provider authentication mechanisms, each of them implementing the
5+
* {@code org.silverpeas.jcr.auth.Authentication} interface.
56
* @author mmoquillon
67
*/
78
public class AuthenticationProvider {
89

9-
private static Authentication authentication = new SilverpeasAuthentication();
10+
private static Authentication[] authentications =
11+
new Authentication[]{
12+
new SQLSimpleAuthentication(),
13+
new TokenAuthentication()};
1014

11-
public static Authentication getAuthentication() {
12-
return authentication;
15+
/**
16+
* Gets all the authentication mechanisms supported by the access controller.
17+
* @return an array of Authentication objects.
18+
*/
19+
public static Authentication[] getAuthentications() {
20+
return authentications;
1321
}
1422
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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

Comments
 (0)