]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java
Can login with any uniquely indexed user property.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / auth / UserAdminLoginModule.java
1 package org.argeo.cms.internal.auth;
2
3 import java.nio.ByteBuffer;
4 import java.nio.CharBuffer;
5 import java.nio.charset.Charset;
6 import java.security.Principal;
7 import java.util.Arrays;
8 import java.util.Map;
9 import java.util.Set;
10
11 import javax.naming.InvalidNameException;
12 import javax.naming.ldap.LdapName;
13 import javax.security.auth.Subject;
14 import javax.security.auth.callback.Callback;
15 import javax.security.auth.callback.CallbackHandler;
16 import javax.security.auth.callback.NameCallback;
17 import javax.security.auth.callback.PasswordCallback;
18 import javax.security.auth.login.CredentialNotFoundException;
19 import javax.security.auth.login.LoginException;
20 import javax.security.auth.spi.LoginModule;
21 import javax.security.auth.x500.X500Principal;
22
23 import org.apache.commons.codec.binary.Base64;
24 import org.apache.commons.codec.digest.DigestUtils;
25 import org.argeo.cms.CmsException;
26 import org.argeo.cms.KernelHeader;
27 import org.argeo.cms.internal.kernel.Activator;
28 import org.osgi.framework.BundleContext;
29 import org.osgi.service.useradmin.Authorization;
30 import org.osgi.service.useradmin.User;
31 import org.osgi.service.useradmin.UserAdmin;
32
33 public class UserAdminLoginModule implements LoginModule {
34 private Subject subject;
35 private CallbackHandler callbackHandler;
36 private boolean isAnonymous = false;
37
38 private final static LdapName ROLE_USER_NAME, ROLE_ANONYMOUS_NAME;
39 private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL;
40 static {
41 try {
42 ROLE_USER_NAME = new LdapName(KernelHeader.ROLE_USER);
43 ROLE_ANONYMOUS_NAME = new LdapName(KernelHeader.ROLE_ANONYMOUS);
44 ROLE_ANONYMOUS_PRINCIPAL = new X500Principal(
45 ROLE_ANONYMOUS_NAME.toString());
46 } catch (InvalidNameException e) {
47 throw new Error("Cannot initialize login module class", e);
48 }
49 }
50
51 private Authorization authorization;
52
53 @Override
54 public void initialize(Subject subject, CallbackHandler callbackHandler,
55 Map<String, ?> sharedState, Map<String, ?> options) {
56 try {
57 this.subject = subject;
58 this.callbackHandler = callbackHandler;
59 if (options.containsKey("anonymous"))
60 isAnonymous = Boolean.parseBoolean(options.get("anonymous")
61 .toString());
62 // String ldifFile = options.get("ldifFile").toString();
63 // InputStream in = new URL(ldifFile).openStream();
64 // userAdmin = new LdifUserAdmin(in);
65 } catch (Exception e) {
66 throw new CmsException("Cannot initialize login module", e);
67 }
68 }
69
70 @Override
71 public boolean login() throws LoginException {
72 // TODO use a callback in order to get the bundle context
73 BundleContext bc = Activator.getBundleContext();
74 UserAdmin userAdmin = bc.getService(bc
75 .getServiceReference(UserAdmin.class));
76 final User user;
77
78 if (!isAnonymous) {
79 // ask for username and password
80 NameCallback nameCallback = new NameCallback("User");
81 PasswordCallback passwordCallback = new PasswordCallback(
82 "Password", false);
83 // handle callbacks
84 try {
85 callbackHandler.handle(new Callback[] { nameCallback,
86 passwordCallback });
87 } catch (Exception e) {
88 throw new CmsException("Cannot handle callbacks", e);
89 }
90
91 // create credentials
92 final String username = nameCallback.getName();
93 if (username == null || username.trim().equals(""))
94 throw new CredentialNotFoundException("No credentials provided");
95
96 char[] password = {};
97 if (passwordCallback.getPassword() != null)
98 password = passwordCallback.getPassword();
99 else
100 throw new CredentialNotFoundException("No credentials provided");
101
102 // user = (User) userAdmin.getRole(username);
103 user = userAdmin.getUser(null, username);
104 if (user == null)
105 return false;
106
107 byte[] hashedPassword = ("{SHA}" + Base64
108 .encodeBase64String(DigestUtils.sha1(toBytes(password))))
109 .getBytes();
110 if (!user.hasCredential("userpassword", hashedPassword))
111 return false;
112 } else
113 // anonymous
114 user = null;
115 this.authorization = userAdmin.getAuthorization(user);
116 return true;
117 }
118
119 private byte[] toBytes(char[] chars) {
120 CharBuffer charBuffer = CharBuffer.wrap(chars);
121 ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
122 byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
123 byteBuffer.position(), byteBuffer.limit());
124 Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
125 Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
126 return bytes;
127 }
128
129 @Override
130 public boolean commit() throws LoginException {
131 if (authorization != null) {
132 Set<Principal> principals = subject.getPrincipals();
133 try {
134 String authName = authorization.getName();
135
136 // determine user'S principal
137 final LdapName name;
138 final Principal userPrincipal;
139 if (authName == null) {
140 name = ROLE_ANONYMOUS_NAME;
141 userPrincipal = ROLE_ANONYMOUS_PRINCIPAL;
142 principals.add(userPrincipal);
143 } else {
144 name = new LdapName(authName);
145 userPrincipal = new X500Principal(name.toString());
146 principals.add(userPrincipal);
147 principals.add(new ImpliedByPrincipal(ROLE_USER_NAME,
148 userPrincipal));
149 }
150
151 // Add roles provided by authorization
152 for (String role : authorization.getRoles()) {
153 LdapName roleName = new LdapName(role);
154 if (ROLE_USER_NAME.equals(roleName))
155 throw new CmsException(ROLE_USER_NAME
156 + " cannot be listed as role");
157 if (ROLE_ANONYMOUS_NAME.equals(roleName))
158 throw new CmsException(ROLE_ANONYMOUS_NAME
159 + " cannot be listed as role");
160 if (roleName.equals(name)) {
161 // skip
162 } else {
163 principals.add(new ImpliedByPrincipal(roleName
164 .toString(), userPrincipal));
165 }
166 }
167
168 return true;
169 } catch (InvalidNameException e) {
170 throw new CmsException("Cannot commit", e);
171 }
172 } else
173 return false;
174 }
175
176 @Override
177 public boolean abort() throws LoginException {
178 cleanUp();
179 return true;
180 }
181
182 @Override
183 public boolean logout() throws LoginException {
184 // TODO better deal with successive logout
185 if (subject == null)
186 return true;
187 // TODO make it less brutal
188 subject.getPrincipals().removeAll(
189 subject.getPrincipals(X500Principal.class));
190 subject.getPrincipals().removeAll(
191 subject.getPrincipals(ImpliedByPrincipal.class));
192 cleanUp();
193 return true;
194 }
195
196 private void cleanUp() {
197 subject = null;
198 authorization = null;
199 }
200 }