From: Mathieu Baudier Date: Tue, 25 Nov 2014 13:10:18 +0000 (+0000) Subject: New project conventions X-Git-Tag: argeo-commons-2.1.30~518 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=10ed6557c631d5feee8541badd0c9f16a9e791c6;p=lgpl%2Fargeo-commons.git New project conventions git-svn-id: https://svn.argeo.org/commons/trunk@7530 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.security.core/.classpath b/org.argeo.security.core/.classpath index b639b537c..91d2c42e5 100644 --- a/org.argeo.security.core/.classpath +++ b/org.argeo.security.core/.classpath @@ -1,10 +1,10 @@ - - - - - >> - - + + + + + diff --git a/org.argeo.security.core/bnd.bnd b/org.argeo.security.core/bnd.bnd new file mode 100644 index 000000000..2bbd9c3b8 --- /dev/null +++ b/org.argeo.security.core/bnd.bnd @@ -0,0 +1,4 @@ +Bundle-ActivationPolicy: lazy +Import-Package:org.bouncycastle.*;resolution:=optional,\ +javax.jcr.security,\ +* diff --git a/org.argeo.security.core/build.properties b/org.argeo.security.core/build.properties index 788870d98..c988eeb5d 100644 --- a/org.argeo.security.core/build.properties +++ b/org.argeo.security.core/build.properties @@ -7,7 +7,6 @@ additional.bundles = org.springframework.transaction,\ org.apache.log4j,\ slf4j.api,\ slf4j.org.apache.commons.logging -source.. = src/main/java/,\ - src/main/resources/,\ - src/test/java/,\ - src/test/resources/ +source.. = src/,\ + ext/test/ +bin.. = bin/ diff --git a/org.argeo.security.core/ext/test/log4j.properties b/org.argeo.security.core/ext/test/log4j.properties new file mode 100644 index 000000000..b33daa9db --- /dev/null +++ b/org.argeo.security.core/ext/test/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=WARN, console + +## Levels +log4j.logger.org.argeo=DEBUG + +log4j.logger.org.hibernate=WARN + +log4j.logger.org.springframework=WARN +#log4j.logger.org.springframework.web=DEBUG +#log4j.logger.org.springframework.jms=WARN +#log4j.logger.org.springframework.security=WARN + +log4j.logger.org.apache.activemq=WARN +log4j.logger.org.apache.activemq.transport=WARN +log4j.logger.org.apache.activemq.ActiveMQMessageConsumer=INFO +log4j.logger.org.apache.activemq.ActiveMQMessageProducer=INFO + +log4j.logger.org.apache.catalina=INFO +log4j.logger.org.apache.coyote=INFO +log4j.logger.org.apache.tomcat=INFO + +## Appenders +# console is set to be a ConsoleAppender. +log4j.appender.console=org.apache.log4j.ConsoleAppender + +# console uses PatternLayout. +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n diff --git a/org.argeo.security.core/ext/test/org/argeo/security/PasswordSandbox.java b/org.argeo.security.core/ext/test/org/argeo/security/PasswordSandbox.java new file mode 100644 index 000000000..d8a084607 --- /dev/null +++ b/org.argeo.security.core/ext/test/org/argeo/security/PasswordSandbox.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder; + +public class PasswordSandbox { + public static void main(String[] args) { + try { + // Tested password + String pwdPlain = "demo"; + + // Check Java generated values + LdapShaPasswordEncoder lspe = new LdapShaPasswordEncoder(); + String pwdLdapShaBase64 = lspe.encodePassword(pwdPlain, null); + System.out.println("pwdLdapShaBase64:\t\t" + pwdLdapShaBase64); + + String pwdShaBase64 = pwdLdapShaBase64.substring("{SHA}".length()); + System.out.println("pwdShaBase64:\t\t\t" + pwdShaBase64); + + byte[] pwdShaArray = Base64.decodeBase64(pwdShaBase64.getBytes()); + String pwdShaHex = new String(Hex.encodeHex(pwdShaArray)); + System.out.println("pwdShaHex:\t\t\t" + pwdShaHex); + + // Check that we can use JavaScript generated values in Hex + String jsShaHex = "89e495e7941cf9e40e6980d14a16bf023ccd4c91"; + System.out.println("jsShaHex:\t\t\t" + pwdShaHex); + System.out.println("pwdShaHex==jsShaHex:\t\t" + + (pwdShaHex.equals(jsShaHex))); + + byte[] jsShaArray = Hex.decodeHex(jsShaHex.toCharArray()); + String jsShaBase64 = new String(Base64.encodeBase64(jsShaArray)); + System.out.println("jsShaBase64:\t\t\t" + jsShaBase64); + System.out.println("pwdShaBase64==jsShaBase64:\t" + + (pwdShaBase64.equals(jsShaBase64))); + } catch (DecoderException e) { + e.printStackTrace(); + } + + } + +} \ No newline at end of file diff --git a/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java b/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java new file mode 100644 index 000000000..6973f5704 --- /dev/null +++ b/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; + +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.StreamUtils; +import org.argeo.security.crypto.PasswordBasedEncryption; + +public class PasswordBasedEncryptionTest extends TestCase { + private final static Log log = LogFactory + .getLog(PasswordBasedEncryptionTest.class); + + public void testEncryptDecrypt() { + final String password = "test long password since they are safer"; + PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption( + password.toCharArray()); + String message = "Hello World!"; + log.info("Password:\t'" + password + "'"); + log.info("Message:\t'" + message + "'"); + byte[] encrypted = pbeEnc.encryptString(message); + log.info("Encrypted:\t'" + + DatatypeConverter.printBase64Binary(encrypted) + "'"); + PasswordBasedEncryption pbeDec = new PasswordBasedEncryption( + password.toCharArray()); + InputStream in = null; + in = new ByteArrayInputStream(encrypted); + String decrypted = pbeDec.decryptAsString(in); + log.info("Decrypted:\t'" + decrypted + "'"); + StreamUtils.closeQuietly(in); + assertEquals(message, decrypted); + } + + public void testPBEWithMD5AndDES() throws Exception { + String password = "test"; + String message = "Hello World!"; + + byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, + (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; + + int count = 1024; + + String cipherAlgorithm = "PBEWithMD5AndDES"; + String secretKeyAlgorithm = "PBEWithMD5AndDES"; + SecretKeyFactory keyFac = SecretKeyFactory + .getInstance(secretKeyAlgorithm); + PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); + PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count); + SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec); + Cipher ecipher = Cipher.getInstance(cipherAlgorithm); + ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec); + Cipher dcipher = Cipher.getInstance(cipherAlgorithm); + dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec); + + byte[] encrypted = ecipher.doFinal(message.getBytes()); + byte[] decrypted = dcipher.doFinal(encrypted); + assertEquals(message, new String(decrypted)); + + } + + public void testPBEWithSHA1AndAES() throws Exception { + String password = "test"; + String message = "Hello World!"; + + byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, + (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; + byte[] iv = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, + (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99, + (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, + (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; + + int count = 1024; + // int keyLength = 256; + int keyLength = 128; + + String cipherAlgorithm = "AES/CBC/PKCS5Padding"; + String secretKeyAlgorithm = "PBKDF2WithHmacSHA1"; + SecretKeyFactory keyFac = SecretKeyFactory + .getInstance(secretKeyAlgorithm); + PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, + count, keyLength); + SecretKey tmp = keyFac.generateSecret(pbeKeySpec); + SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); + Cipher ecipher = Cipher.getInstance(cipherAlgorithm); + ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv)); + + // decrypt + keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm); + pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count, + keyLength); + tmp = keyFac.generateSecret(pbeKeySpec); + secret = new SecretKeySpec(tmp.getEncoded(), "AES"); + // AlgorithmParameters params = ecipher.getParameters(); + // byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); + Cipher dcipher = Cipher.getInstance(cipherAlgorithm); + dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); + + byte[] encrypted = ecipher.doFinal(message.getBytes()); + byte[] decrypted = dcipher.doFinal(encrypted); + assertEquals(message, new String(decrypted)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher); + cipherOut.write(message.getBytes()); + StreamUtils.closeQuietly(cipherOut); + byte[] enc = out.toByteArray(); + + ByteArrayInputStream in = new ByteArrayInputStream(enc); + CipherInputStream cipherIn = new CipherInputStream(in, dcipher); + ByteArrayOutputStream dec = new ByteArrayOutputStream(); + StreamUtils.copy(cipherIn, dec); + assertEquals(message, new String(dec.toByteArray())); + } +} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java b/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java deleted file mode 100644 index 1870675d8..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import org.springframework.security.GrantedAuthority; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - -/** Credentials required for the authentication to a node. */ -public class NodeAuthenticationToken extends - UsernamePasswordAuthenticationToken { - private static final long serialVersionUID = 1955222132884795213L; - private final String url; - - /** Non authenticated local constructor */ - public NodeAuthenticationToken(Object principal, Object credentials) { - super(principal, credentials); - this.url = null; - } - - /** Non authenticated remote constructor */ - public NodeAuthenticationToken(Object principal, Object credentials, - String url) { - super(principal, credentials); - this.url = url; - } - - /** Authenticated constructor */ - public NodeAuthenticationToken(NodeAuthenticationToken sat, - GrantedAuthority[] authorities) { - super(sat.getPrincipal(), sat.getCredentials(), authorities); - this.url = sat.getUrl(); - } - - public String getUrl() { - return url; - } - - public Boolean isRemote() { - return url != null; - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java b/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java deleted file mode 100644 index b3727b26f..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/OsAuthenticationToken.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import java.security.AccessController; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import javax.security.auth.Subject; - -import org.argeo.ArgeoException; -import org.argeo.OperatingSystem; -import org.springframework.security.Authentication; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.userdetails.UserDetails; - -/** Abstracts principals provided by com.sun.security.auth.module login modules. */ -public class OsAuthenticationToken implements Authentication { - private static final long serialVersionUID = -7544626794250917244L; - - final Class osUserPrincipalClass; - final Class osUserIdPrincipalClass; - final Class osGroupIdPrincipalClass; - - private List grantedAuthorities; - - private UserDetails details; - - /** Request */ - public OsAuthenticationToken(GrantedAuthority[] grantedAuthorities) { - this.grantedAuthorities = grantedAuthorities != null ? Arrays - .asList(grantedAuthorities) : null; - ClassLoader cl = getClass().getClassLoader(); - switch (OperatingSystem.os) { - case OperatingSystem.WINDOWS: - osUserPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.NTUserPrincipal"); - osUserIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.NTSidUserPrincipal"); - osGroupIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.NTSidGroupPrincipal"); - break; - case OperatingSystem.NIX: - osUserPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.UnixPrincipal"); - osUserIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.UnixNumericUserPrincipal"); - osGroupIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.UnixNumericGroupPrincipal"); - break; - case OperatingSystem.SOLARIS: - osUserPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.SolarisPrincipal"); - osUserIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.SolarisNumericUserPrincipal"); - osGroupIdPrincipalClass = getPrincipalClass(cl, - "com.sun.security.auth.SolarisNumericGroupPrincipal"); - break; - - default: - throw new ArgeoException("Unsupported operating system " - + OperatingSystem.os); - } - - } - - /** Authenticated */ - public OsAuthenticationToken() { - this(null); - } - - /** @return the name, or null if not yet logged */ - public String getName() { - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject == null) - return null; - return getUser().getName(); - } - - /** - * Should not be called during authentication since group IDs are not yet - * available {@link Subject} has been set - */ - public GrantedAuthority[] getAuthorities() { - // grantedAuthorities should not be null at this stage - List gas = new ArrayList( - grantedAuthorities); - for (Principal groupPrincipal : getGroupsIds()) { - gas.add(new GrantedAuthorityImpl("OSGROUP_" - + groupPrincipal.getName())); - } - return gas.toArray(new GrantedAuthority[gas.size()]); - } - - public UserDetails getDetails() { - return details; - } - - public void setDetails(UserDetails details) { - this.details = details; - } - - public boolean isAuthenticated() { - return grantedAuthorities != null; - } - - public void setAuthenticated(boolean isAuthenticated) - throws IllegalArgumentException { - if (grantedAuthorities != null) - grantedAuthorities.clear(); - grantedAuthorities = null; - } - - @SuppressWarnings("unchecked") - protected static Class getPrincipalClass( - ClassLoader cl, String className) { - try { - return (Class) cl.loadClass(className); - } catch (ClassNotFoundException e) { - throw new ArgeoException("Cannot load principal class", e); - } - } - - public Object getPrincipal() { - return getUser(); - } - - public Principal getUser() { - Subject subject = getSubject(); - Set userPrincipals = subject - .getPrincipals(osUserPrincipalClass); - if (userPrincipals == null || userPrincipals.size() == 0) - throw new ArgeoException("No OS principal"); - if (userPrincipals.size() > 1) - throw new ArgeoException("More than one OS principal"); - Principal user = userPrincipals.iterator().next(); - return user; - } - - public Principal getUserId() { - Subject subject = getSubject(); - Set userIdsPrincipals = subject - .getPrincipals(osUserIdPrincipalClass); - if (userIdsPrincipals == null || userIdsPrincipals.size() == 0) - throw new ArgeoException("No user id principal"); - if (userIdsPrincipals.size() > 1) - throw new ArgeoException("More than one user id principal"); - Principal userId = userIdsPrincipals.iterator().next(); - return userId; - } - - public Set getGroupsIds() { - Subject subject = getSubject(); - return (Set) subject - .getPrincipals(osGroupIdPrincipalClass); - } - - /** @return the subject always non null */ - protected Subject getSubject() { - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject == null) - throw new ArgeoException("No subject in JAAS context"); - return subject; - } - - public Object getCredentials() { - return ""; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java b/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java deleted file mode 100644 index e5b8ae79c..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/SecurityUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.security.Authentication; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.context.SecurityContext; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; - -/** Static utilities */ -public class SecurityUtils { - - private SecurityUtils() { - } - - /** Whether the current thread has the admin role */ - public static boolean hasCurrentThreadAuthority(String authority) { - SecurityContext securityContext = SecurityContextHolder.getContext(); - if (securityContext != null) { - Authentication authentication = securityContext.getAuthentication(); - if (authentication != null) { - for (GrantedAuthority ga : authentication.getAuthorities()) - if (ga.getAuthority().equals(authority)) - return true; - } - } - return false; - } - - /** - * @return the authenticated username or null if not authenticated / - * anonymous - */ - public static String getCurrentThreadUsername() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - if (securityContext != null) { - Authentication authentication = securityContext.getAuthentication(); - if (authentication != null) { - if (authentication instanceof AnonymousAuthenticationToken) { - return null; - } - return authentication.getName(); - } - } - return null; - } - - /** - * Returns the display name of the user details (by calling toString() on - * it) - */ - public static String getUserDetailsDisplayName() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - if (securityContext != null) { - Authentication authentication = securityContext.getAuthentication(); - if (authentication != null) { - if (authentication instanceof AnonymousAuthenticationToken) { - return null; - } - Object details = authentication.getDetails(); - if (details != null) - return details.toString(); - return authentication.getName(); - } - } - return null; - } - - /** - * Converts an array of Spring Security {@link GrantedAuthority} to a - * read-only list of strings, for portability and integration - */ - public static List authoritiesToStringList( - GrantedAuthority[] authorities) { - List lst = new ArrayList(); - for (GrantedAuthority ga : authorities) - lst.add(ga.getAuthority()); - return Collections.unmodifiableList(lst); - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/SystemAuthentication.java b/org.argeo.security.core/src/main/java/org/argeo/security/SystemAuthentication.java deleted file mode 100644 index 2722c1f7d..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/SystemAuthentication.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -/** - * Marks a system authentication, that is which did not require a login process. - */ -public interface SystemAuthentication { - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/SystemExecutionService.java b/org.argeo.security.core/src/main/java/org/argeo/security/SystemExecutionService.java deleted file mode 100644 index 075a6c3d8..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/SystemExecutionService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; - -/** - * Allows to execute code authenticated as a system user (that is not a real - * person). The {@link Executor} interface is not used directly in order to - * allow future extension of this interface and to simplify its publication - * (e.g. as an OSGi service) and interception. - */ -public interface SystemExecutionService extends Executor { - /** - * Executes this {@link Runnable} within a system authenticated context. - * Implementations should make sure that this method is properly secured via - * Java permissions since it could access everything without credentials. - */ - public void execute(Runnable runnable); - - /** - * Executes this {@link Callable} within a system authenticated context. - * Implementations should make sure that this method is properly secured via - * Java permissions since it could access everything without credentials. - */ - public Future submit(Callable task); -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java b/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java deleted file mode 100644 index 0a84cf66f..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/UserAdminService.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import java.util.Set; - -import org.springframework.security.userdetails.UserDetailsManager; - -/** Enrich {@link UserDetailsManager} in order to provide roles semantics. */ -public interface UserAdminService extends UserDetailsManager { - /** - * Usernames must match this regexp pattern ({@value #USERNAME_PATTERN}). - * Thanks to this tip (modified to add upper-case, add '@') - */ - //public final static String USERNAME_PATTERN = "^[a-zA-Z0-9_-@]{3,64}$"; - - /** - * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. - * Thanks to this tip. - */ - public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; - - /* - * USERS - */ - /** List all users. */ - public Set listUsers(); - - /** List users having this role (except the super user). */ - public Set listUsersInRole(String role); - - /** Synchronize with the underlying DAO. */ - public void synchronize(); - - /* - * ROLES - */ - public void newRole(String role); - - public Set listEditableRoles(); - - public void deleteRole(String role); -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/AbstractSystemExecution.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/AbstractSystemExecution.java deleted file mode 100644 index b84f3de00..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/AbstractSystemExecution.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.security.SystemAuthentication; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationManager; -import org.springframework.security.context.SecurityContext; -import org.springframework.security.context.SecurityContextHolder; - -/** Provides base method for executing code with system authorization. */ -public abstract class AbstractSystemExecution { - static { - // Forces Spring Security to use inheritable strategy - // FIXME find a better place for forcing spring security mode - // doesn't work for the time being -// if (System.getProperty(SecurityContextHolder.SYSTEM_PROPERTY) == null) -// SecurityContextHolder -// .setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); - } - - private final static Log log = LogFactory - .getLog(AbstractSystemExecution.class); - private AuthenticationManager authenticationManager; - private String systemAuthenticationKey; - - /** Whether the current thread was authenticated by this component. */ - private ThreadLocal authenticatedBySelf = new ThreadLocal() { - protected Boolean initialValue() { - return false; - } - }; - - /** - * Authenticate the calling thread to the underlying - * {@link AuthenticationManager} - */ - protected void authenticateAsSystem() { - if (authenticatedBySelf.get()) - return; - SecurityContext securityContext = SecurityContextHolder.getContext(); - Authentication currentAuth = securityContext.getAuthentication(); - if (currentAuth != null) { - if (!(currentAuth instanceof SystemAuthentication)) - throw new ArgeoException( - "System execution on an already authenticated thread: " - + currentAuth + ", THREAD=" - + Thread.currentThread().getId()); - return; - } - // Subject subject = Subject.getSubject(AccessController.getContext()); - // if (subject != null - // && !subject.getPrincipals(Authentication.class).isEmpty()) - // throw new ArgeoException( - // "There is already an authenticated subject: " + subject); - - String key = systemAuthenticationKey != null ? systemAuthenticationKey - : System.getProperty( - InternalAuthentication.SYSTEM_KEY_PROPERTY, - InternalAuthentication.SYSTEM_KEY_DEFAULT); - if (key == null) - throw new ArgeoException("No system key defined"); - Authentication auth = authenticationManager - .authenticate(new InternalAuthentication(key)); - securityContext.setAuthentication(auth); - authenticatedBySelf.set(true); - if (log.isTraceEnabled()) - log.trace("System authenticated"); - } - - // /** Removes the authentication from the calling thread. */ - // protected void deauthenticateAsSystem() { - // // remove the authentication - // // SecurityContext securityContext = SecurityContextHolder.getContext(); - // // securityContext.setAuthentication(null); - // // authenticatedBySelf.set(false); - // if (log.isTraceEnabled()) { - // log.trace("System deauthenticated"); - // // Thread.dumpStack(); - // } - // } - - /** - * Whether the current thread was authenticated by this component or a - * parent thread. - */ - protected Boolean isAuthenticatedBySelf() { - return authenticatedBySelf.get(); - } - - public void setAuthenticationManager( - AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - public void setSystemAuthenticationKey(String systemAuthenticationKey) { - this.systemAuthenticationKey = systemAuthenticationKey; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/AsyncSystemTaskExecutor.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/AsyncSystemTaskExecutor.java deleted file mode 100644 index 0e400c82d..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/AsyncSystemTaskExecutor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import org.argeo.security.SystemExecutionService; -import org.springframework.core.task.SimpleAsyncTaskExecutor; - -/** - * Asynchronous Spring TaskExecutor (for use in JMS for example) wrapping a - * {@link SystemExecutionService}. - */ -public class AsyncSystemTaskExecutor extends SimpleAsyncTaskExecutor { - private static final long serialVersionUID = -8035527542087963068L; - - private SystemExecutionService systemExecutionService; - - public AsyncSystemTaskExecutor() { - super(); - } - - public AsyncSystemTaskExecutor(String threadNamePrefix) { - super(threadNamePrefix); - } - - @Override - public Thread createThread(final Runnable runnable) { - Runnable systemExecutionRunnable = new Runnable() { - - public void run() { - systemExecutionService.execute(runnable); - - } - }; - return super.createThread(systemExecutionRunnable); - } - - public void setSystemExecutionService( - SystemExecutionService systemExecutionService) { - this.systemExecutionService = systemExecutionService; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java deleted file mode 100644 index 97dd6cae0..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import java.beans.PropertyDescriptor; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.beans.BeansException; -import org.springframework.beans.PropertyValues; -import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; - -/** - * Executes with a system authentication the instantiation and initialization - * methods of the application context where it has been defined. - */ -public class AuthenticatedApplicationContextInitialization extends - AbstractSystemExecution implements InstantiationAwareBeanPostProcessor, - ApplicationListener { - // private Log log = LogFactory - // .getLog(AuthenticatedApplicationContextInitialization.class); - /** If non empty, restricts to these beans */ - private List beanNames = new ArrayList(); - - @SuppressWarnings("rawtypes") - public Object postProcessBeforeInstantiation(Class beanClass, - String beanName) throws BeansException { - // we authenticate when any bean is instantiated - // we will deauthenticate only when the application context has been - // refreshed in order to be able to deal with factory beans has well - if (!isAuthenticatedBySelf()) { - if (beanNames.size() == 0) - authenticateAsSystem(); - else if (beanNames.contains(beanName)) - authenticateAsSystem(); - } - return null; - } - - public boolean postProcessAfterInstantiation(Object bean, String beanName) - throws BeansException { - return true; - } - - public PropertyValues postProcessPropertyValues(PropertyValues pvs, - PropertyDescriptor[] pds, Object bean, String beanName) - throws BeansException { - return pvs; - } - - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - // authenticateAsSystem(); - return bean; - } - - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - // NOTE: in case there was an exception in on the initialization method - // we expect the underlying thread to die and thus the system - // authentication to be lost. We have currently no way to catch the - // exception and perform the deauthentication by ourselves. - // deauthenticateAsSystem(); - return bean; - } - - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) { - // make sure that we have deauthenticated after the application - // context was initialized/refreshed - // deauthenticateAsSystem(); - } - } - - public void setBeanNames(List beanNames) { - this.beanNames = beanNames; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java deleted file mode 100644 index 317815e8b..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/AuthenticationProvidersRegister.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * Maintains a list of authentication providers injected in to a provider - * manager, in order to avoid issues with OSGi services and use packages. - */ -public class AuthenticationProvidersRegister implements InitializingBean { - private Log log = LogFactory.getLog(AuthenticationProvidersRegister.class); - - private List providers = new ArrayList(); - private List defaultProviders = new ArrayList(); - - public void register(Object authenticationProvider, - Map parameters) { - providers.add(authenticationProvider); - if (log.isTraceEnabled()) - log.trace("Registered authentication provider " + parameters); - } - - public void unregister(Object authenticationProvider, - Map parameters) { - providers.remove(authenticationProvider); - if (log.isTraceEnabled()) - log.trace("Unregistered authentication provider " + parameters); - } - - public List getProviders() { - return providers; - } - - public void setDefaultProviders( - List defaultProviders) { - this.defaultProviders = defaultProviders; - } - - public void afterPropertiesSet() throws Exception { - providers.addAll(defaultProviders); - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/ConsoleCallbackHandler.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/ConsoleCallbackHandler.java deleted file mode 100644 index faa81b004..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/ConsoleCallbackHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.security.core; - -import java.io.Console; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Locale; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.ArgeoException; -import org.argeo.util.LocaleCallback; - -/** Callback handler to be used with a command line UI. */ -public class ConsoleCallbackHandler implements CallbackHandler { - - @Override - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { - Console console = System.console(); - if (console == null) - throw new ArgeoException("No console available"); - - PrintWriter writer = console.writer(); - for (int i = 0; i < callbacks.length; i++) { - if (callbacks[i] instanceof TextOutputCallback) { - TextOutputCallback callback = (TextOutputCallback) callbacks[i]; - writer.write(callback.getMessage()); - } else if (callbacks[i] instanceof NameCallback) { - NameCallback callback = (NameCallback) callbacks[i]; - writer.write(callback.getPrompt()); - if (callback.getDefaultName() != null) - writer.write(" (" + callback.getDefaultName() + ")"); - writer.write(" : "); - String answer = console.readLine(); - if (callback.getDefaultName() != null - && answer.trim().equals("")) - callback.setName(callback.getDefaultName()); - else - callback.setName(answer); - } else if (callbacks[i] instanceof PasswordCallback) { - PasswordCallback callback = (PasswordCallback) callbacks[i]; - writer.write(callback.getPrompt()); - char[] answer = console.readPassword(); - callback.setPassword(answer); - Arrays.fill(answer, ' '); - } else if (callbacks[i] instanceof LocaleCallback) { - LocaleCallback callback = (LocaleCallback) callbacks[i]; - writer.write(callback.getPrompt()); - writer.write("\n"); - for (int j = 0; j < callback.getAvailableLocales().size(); j++) { - Locale locale = callback.getAvailableLocales().get(j); - writer.print(j + " : " + locale.getDisplayName() + "\n"); - } - writer.write("(" + callback.getDefaultIndex() + ") : "); - String answer = console.readLine(); - if (answer.trim().equals("")) - callback.setSelectedIndex(callback.getDefaultIndex()); - else - callback.setSelectedIndex(new Integer(answer.trim())); - } - } - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java deleted file mode 100644 index 267ddd312..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import org.argeo.security.SystemAuthentication; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.adapters.PrincipalSpringSecurityUserToken; - -/** A token base on a system key used to request a system authentication. */ -public class InternalAuthentication extends PrincipalSpringSecurityUserToken - implements SystemAuthentication { - private static final long serialVersionUID = -6783376375615949315L; - /** 'admin' for consistency with JCR */ - public final static String DEFAULT_SYSTEM_USERNAME = "admin"; - public final static String DEFAULT_SYSTEM_ROLE = "ROLE_SYSTEM"; - public final static String SYSTEM_KEY_PROPERTY = "argeo.security.systemKey"; - public final static String SYSTEM_KEY_DEFAULT = "argeo"; - - public InternalAuthentication(String key, String systemUsername, - String systemRole) { - super( - key, - systemUsername, - key, - new GrantedAuthority[] { new GrantedAuthorityImpl(systemRole) }, - systemUsername); - } - - public InternalAuthentication(String key) { - this(key, DEFAULT_SYSTEM_USERNAME, DEFAULT_SYSTEM_ROLE); - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/KeyBasedSystemExecutionService.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/KeyBasedSystemExecutionService.java deleted file mode 100644 index 6c85df1d1..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/KeyBasedSystemExecutionService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; - -import org.argeo.ArgeoException; -import org.argeo.security.SystemExecutionService; - -/** - * Implementation of a {@link SystemExecutionService} using a key-based - * {@link InternalAuthentication} - */ -public class KeyBasedSystemExecutionService extends AbstractSystemExecution - implements SystemExecutionService { - public void execute(Runnable runnable) { - try { - wrapWithSystemAuthentication(Executors.callable(runnable)).call(); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new ArgeoException( - "Exception when running system authenticated task", e); - } - } - - public Future submit(Callable task) { - FutureTask future = new FutureTask( - wrapWithSystemAuthentication(task)); - future.run(); - return future; - } - - protected Callable wrapWithSystemAuthentication( - final Callable runnable) { - return new Callable() { - - public T call() throws Exception { - authenticateAsSystem(); - try { - return runnable.call(); - } finally { -// deauthenticateAsSystem(); - } - } - }; - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java deleted file mode 100644 index 0471151a2..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.springframework.core.io.Resource; -import org.springframework.security.AuthenticationException; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; -import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; -import org.springframework.security.userdetails.User; -import org.springframework.security.userdetails.UserDetails; - -/** @deprecated */ -@Deprecated -public class MatchingAuthenticationProvider extends - AbstractUserDetailsAuthenticationProvider { - - private Resource mapping; - private Properties properties; - - private List defaultRoles = new ArrayList(); - - @Override - protected void doAfterPropertiesSet() throws Exception { - properties = new Properties(); - InputStream propIn = mapping.getInputStream(); - try { - properties.load(propIn); - } finally { - propIn.close(); - } - } - - @Override - protected void additionalAuthenticationChecks(UserDetails userDetails, - UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - if (!userDetails.getPassword().equals(authentication.getCredentials())) - throw new BadCredentialsException( - "Invalid credentails provided by " - + authentication.getName()); - } - - @Override - protected UserDetails retrieveUser(String username, - UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - String value = properties.getProperty(username); - if (value == null) - throw new BadCredentialsException("User " + username - + " is not registered"); - List grantedAuthorities = new ArrayList(); - for (String role : defaultRoles) - grantedAuthorities.add(new GrantedAuthorityImpl(role)); - return new User( - username, - value, - true, - true, - true, - true, - grantedAuthorities - .toArray(new GrantedAuthority[grantedAuthorities.size()])); - } - - public void setMapping(Resource mapping) { - this.mapping = mapping; - } - - public void setDefaultRoles(List defaultRoles) { - this.defaultRoles = defaultRoles; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java deleted file mode 100644 index 0e29ecd59..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/OsAuthenticationProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.core; - -import java.util.ArrayList; -import java.util.List; - -import org.argeo.security.OsAuthenticationToken; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.providers.AuthenticationProvider; - -/** - * Validates an OS authentication. The id is that it will always be - * authenticated since we are always runnign within an OS, but the fact that the - * {@link Authentication} works properly depends on the proper OS login module - * having been called as well. TODO make it more configurable (base roles, is - * admin) - */ -public class OsAuthenticationProvider implements AuthenticationProvider { - final static String osUserRole = "ROLE_OS_USER"; - final static String userRole = "ROLE_USER"; - final static String adminRole = "ROLE_ADMIN"; - - final static Boolean isAdmin = true; - - public Authentication authenticate(Authentication authentication) - throws AuthenticationException { - return new OsAuthenticationToken(getBaseAuthorities()); - } - - public static GrantedAuthority[] getBaseAuthorities() { - List auths = new ArrayList(); - auths.add(new GrantedAuthorityImpl(osUserRole)); - auths.add(new GrantedAuthorityImpl(userRole)); - if (isAdmin) - auths.add(new GrantedAuthorityImpl(adminRole)); - return auths.toArray(new GrantedAuthority[auths.size()]); - } - - @SuppressWarnings("rawtypes") - public boolean supports(Class authentication) { - return OsAuthenticationToken.class.isAssignableFrom(authentication); - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/OsgiModuleLabel.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/OsgiModuleLabel.java deleted file mode 100644 index 45c9e16b0..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/OsgiModuleLabel.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.security.core; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; - -/** - * Logs the name and version of an OSGi bundle based on its - * {@link BundleContext}. - */ -public class OsgiModuleLabel { - private final static Log log = LogFactory.getLog(OsgiModuleLabel.class); - - private Bundle bundle; - - public OsgiModuleLabel() { - } - - /** Sets without logging. */ - public OsgiModuleLabel(Bundle bundle) { - this.bundle = bundle; - } - - /** - * Retrieved bundle from a bundle context and logs it. Typically to be set - * as a Spring bean. - */ - public void setBundleContext(BundleContext bundleContext) { - this.bundle = bundleContext.getBundle(); - log.info(msg()); - } - - public String msg() { - String name = bundle.getHeaders().get(Constants.BUNDLE_NAME).toString(); - String symbolicName = bundle.getSymbolicName(); - String version = bundle.getVersion().toString(); - return name + " v" + version + " (" + symbolicName + ")"; - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/core/SimpleRoleRegistration.java b/org.argeo.security.core/src/main/java/org/argeo/security/core/SimpleRoleRegistration.java deleted file mode 100644 index aa8a5f06e..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/core/SimpleRoleRegistration.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.argeo.security.core; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.security.UserAdminService; - -/** - * Register one or many roles via a user admin service. Does nothing if the role - * is already registered. - */ -public class SimpleRoleRegistration implements Runnable { - private final static Log log = LogFactory - .getLog(SimpleRoleRegistration.class); - - private String role; - private List roles = new ArrayList(); - private UserAdminService userAdminService; - - @Override - public void run() { - Set existingRoles = userAdminService.listEditableRoles(); - if (role != null && !existingRoles.contains(role)) - newRole(role); - for (String r : roles) { - if (!existingRoles.contains(r)) - newRole(r); - } - } - - protected void newRole(String r) { - userAdminService.newRole(r); - log.info("Added role " + r + " required by application."); - } - - public void register(UserAdminService userAdminService, Map properties) { - this.userAdminService = userAdminService; - run(); - } - - public void setRole(String role) { - this.role = role; - } - - public void setRoles(List roles) { - this.roles = roles; - } - - public void setUserAdminService(UserAdminService userAdminService) { - this.userAdminService = userAdminService; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java b/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java deleted file mode 100644 index daa1ebd12..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.security.AccessController; -import java.security.MessageDigest; -import java.security.Provider; -import java.security.Security; -import java.util.Arrays; -import java.util.Iterator; - -import javax.crypto.SecretKey; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.ArgeoException; -import org.argeo.StreamUtils; -import org.argeo.util.security.Keyring; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** username / password based keyring. TODO internationalize */ -public abstract class AbstractKeyring implements Keyring, CryptoKeyring { - static { - Security.addProvider(new BouncyCastleProvider()); - } - - public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING"; - - private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT; - private CallbackHandler defaultCallbackHandler; - - private String charset = "UTF-8"; - - /** - * Default provider is bouncy castle, in order to have consistent behaviour - * across implementations - */ - private String securityProviderName = "BC"; - - /** - * Whether the keyring has already been created in the past with a master - * password - */ - protected abstract Boolean isSetup(); - - /** - * Setup the keyring persistently, {@link #isSetup()} must return true - * afterwards - */ - protected abstract void setup(char[] password); - - /** Populates the key spec callback */ - protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback); - - protected abstract void encrypt(String path, InputStream unencrypted); - - protected abstract InputStream decrypt(String path); - - /** Triggers lazy initialization */ - protected SecretKey getSecretKey() { - Subject subject = Subject.getSubject(AccessController.getContext()); - // we assume only one secrete key is available - Iterator iterator = subject.getPrivateCredentials( - SecretKey.class).iterator(); - if (!iterator.hasNext()) {// not initialized - CallbackHandler callbackHandler = new KeyringCallbackHandler(); - try { - LoginContext loginContext = new LoginContext(loginContextName, - subject, callbackHandler); - loginContext.login(); - // FIXME will login even if password is wrong - iterator = subject.getPrivateCredentials(SecretKey.class) - .iterator(); - return iterator.next(); - } catch (LoginException e) { - throw new ArgeoException("Keyring login failed", e); - } - - } else { - SecretKey secretKey = iterator.next(); - if (iterator.hasNext()) - throw new ArgeoException( - "More than one secret key in private credentials"); - return secretKey; - } - } - - public InputStream getAsStream(String path) { - return decrypt(path); - } - - public void set(String path, InputStream in) { - encrypt(path, in); - } - - public char[] getAsChars(String path) { - InputStream in = getAsStream(path); - CharArrayWriter writer = null; - Reader reader = null; - try { - writer = new CharArrayWriter(); - reader = new InputStreamReader(in, charset); - StreamUtils.copy(reader, writer); - return writer.toCharArray(); - } catch (IOException e) { - throw new ArgeoException("Cannot decrypt to char array", e); - } finally { - StreamUtils.closeQuietly(reader); - StreamUtils.closeQuietly(in); - StreamUtils.closeQuietly(writer); - } - } - - public void set(String path, char[] arr) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayInputStream in = null; - Writer writer = null; - try { - writer = new OutputStreamWriter(out, charset); - writer.write(arr); - writer.flush(); - in = new ByteArrayInputStream(out.toByteArray()); - set(path, in); - } catch (IOException e) { - throw new ArgeoException("Cannot encrypt to char array", e); - } finally { - StreamUtils.closeQuietly(writer); - StreamUtils.closeQuietly(out); - StreamUtils.closeQuietly(in); - } - } - - protected Provider getSecurityProvider() { - return Security.getProvider(securityProviderName); - } - - public void setLoginContextName(String loginContextName) { - this.loginContextName = loginContextName; - } - - public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) { - this.defaultCallbackHandler = defaultCallbackHandler; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public void setSecurityProviderName(String securityProviderName) { - this.securityProviderName = securityProviderName; - } - - @Deprecated - protected static byte[] hash(char[] password, byte[] salt, - Integer iterationCount) { - ByteArrayOutputStream out = null; - OutputStreamWriter writer = null; - try { - out = new ByteArrayOutputStream(); - writer = new OutputStreamWriter(out, "UTF-8"); - writer.write(password); - MessageDigest pwDigest = MessageDigest.getInstance("SHA-256"); - pwDigest.reset(); - pwDigest.update(salt); - byte[] btPass = pwDigest.digest(out.toByteArray()); - for (int i = 0; i < iterationCount; i++) { - pwDigest.reset(); - btPass = pwDigest.digest(btPass); - } - return btPass; - } catch (Exception e) { - throw new ArgeoException("Cannot hash", e); - } finally { - StreamUtils.closeQuietly(out); - StreamUtils.closeQuietly(writer); - } - - } - - /** - * Convenience method using the underlying callback to ask for a password - * (typically used when the password is not saved in the keyring) - */ - protected char[] ask() { - PasswordCallback passwordCb = new PasswordCallback("Password", false); - Callback[] dialogCbs = new Callback[] { passwordCb }; - try { - defaultCallbackHandler.handle(dialogCbs); - char[] password = passwordCb.getPassword(); - return password; - } catch (Exception e) { - throw new ArgeoException("Cannot ask for a password", e); - } - - } - - class KeyringCallbackHandler implements CallbackHandler { - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { - // checks - if (callbacks.length != 2) - throw new IllegalArgumentException( - "Keyring required 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); - if (!(callbacks[0] instanceof PasswordCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - if (!(callbacks[1] instanceof PBEKeySpecCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - - PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; - PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; - - if (isSetup()) { - Callback[] dialogCbs = new Callback[] { passwordCb }; - defaultCallbackHandler.handle(dialogCbs); - } else {// setup keyring - TextOutputCallback textCb1 = new TextOutputCallback( - TextOutputCallback.INFORMATION, - "Enter a master password which will protect your private data"); - TextOutputCallback textCb2 = new TextOutputCallback( - TextOutputCallback.INFORMATION, - "(for example your credentials to third-party services)"); - TextOutputCallback textCb3 = new TextOutputCallback( - TextOutputCallback.INFORMATION, - "Don't forget this password since the data cannot be read without it"); - PasswordCallback confirmPasswordCb = new PasswordCallback( - "Confirm password", false); - // first try - Callback[] dialogCbs = new Callback[] { textCb1, textCb2, - textCb3, passwordCb, confirmPasswordCb }; - defaultCallbackHandler.handle(dialogCbs); - - // if passwords different, retry (except if cancelled) - while (passwordCb.getPassword() != null - && !Arrays.equals(passwordCb.getPassword(), - confirmPasswordCb.getPassword())) { - TextOutputCallback textCb = new TextOutputCallback( - TextOutputCallback.ERROR, - "The passwords do not match"); - dialogCbs = new Callback[] { textCb, passwordCb, - confirmPasswordCb }; - defaultCallbackHandler.handle(dialogCbs); - } - - if (passwordCb.getPassword() != null) {// not cancelled - setup(passwordCb.getPassword()); - } - } - - if (passwordCb.getPassword() != null) - handleKeySpecCallback(pbeCb); - } - - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/CryptoKeyring.java b/org.argeo.security.core/src/main/java/org/argeo/security/crypto/CryptoKeyring.java deleted file mode 100644 index d25eccd22..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/CryptoKeyring.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import org.argeo.util.security.Keyring; - -/** - * Advanced keyring based on cryptography that can easily be centralized and - * coordinated with {@link KeyringLoginModule} (since they ar ein the same - * package) - */ -public interface CryptoKeyring extends Keyring { - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java b/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java deleted file mode 100644 index 34b7d4015..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import java.security.AccessController; -import java.util.Map; -import java.util.Set; - -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; - -/** Adds a secret key to the private credentials */ -public class KeyringLoginModule implements LoginModule { - private Subject subject; - private CallbackHandler callbackHandler; - private SecretKey secretKey; - - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) { - this.subject = subject; - if (subject == null) { - subject = Subject.getSubject(AccessController.getContext()); - } - this.callbackHandler = callbackHandler; - } - - public boolean login() throws LoginException { - Set pbes = subject.getPrivateCredentials(SecretKey.class); - if (pbes.size() > 0) - return true; - PasswordCallback pc = new PasswordCallback("Master password", false); - PBEKeySpecCallback pbeCb = new PBEKeySpecCallback(); - Callback[] callbacks = { pc, pbeCb }; - try { - callbackHandler.handle(callbacks); - char[] password = pc.getPassword(); - - SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb - .getSecretKeyFactory()); - PBEKeySpec keySpec; - if (pbeCb.getKeyLength() != null) - keySpec = new PBEKeySpec(password, pbeCb.getSalt(), - pbeCb.getIterationCount(), pbeCb.getKeyLength()); - else - keySpec = new PBEKeySpec(password, pbeCb.getSalt(), - pbeCb.getIterationCount()); - - String secKeyEncryption = pbeCb.getSecretKeyEncryption(); - if (secKeyEncryption != null) { - SecretKey tmp = keyFac.generateSecret(keySpec); - secretKey = new SecretKeySpec(tmp.getEncoded(), - secKeyEncryption); - } else { - secretKey = keyFac.generateSecret(keySpec); - } - } catch (Exception e) { - LoginException le = new LoginException("Cannot login keyring"); - le.initCause(e); - throw le; - } - return true; - } - - public boolean commit() throws LoginException { - if (secretKey != null) - subject.getPrivateCredentials().add(secretKey); - return true; - } - - public boolean abort() throws LoginException { - return true; - } - - public boolean logout() throws LoginException { - Set pbes = subject - .getPrivateCredentials(PasswordBasedEncryption.class); - pbes.clear(); - return true; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java b/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java deleted file mode 100644 index e96436664..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import javax.crypto.spec.PBEKeySpec; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.PasswordCallback; - -/** - * All information required to set up a {@link PBEKeySpec} bar the password - * itself (use a {@link PasswordCallback}) - */ -public class PBEKeySpecCallback implements Callback { - private String secretKeyFactory; - private byte[] salt; - private Integer iterationCount; - /** Can be null for some algorithms */ - private Integer keyLength; - /** Can be null, will trigger secret key encryption if not */ - private String secretKeyEncryption; - - private String encryptedPasswordHashCipher; - private byte[] encryptedPasswordHash; - - public void set(String secretKeyFactory, byte[] salt, - Integer iterationCount, Integer keyLength, - String secretKeyEncryption) { - this.secretKeyFactory = secretKeyFactory; - this.salt = salt; - this.iterationCount = iterationCount; - this.keyLength = keyLength; - this.secretKeyEncryption = secretKeyEncryption; -// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher; -// this.encryptedPasswordHash = encryptedPasswordHash; - } - - public String getSecretKeyFactory() { - return secretKeyFactory; - } - - public byte[] getSalt() { - return salt; - } - - public Integer getIterationCount() { - return iterationCount; - } - - public Integer getKeyLength() { - return keyLength; - } - - public String getSecretKeyEncryption() { - return secretKeyEncryption; - } - - public String getEncryptedPasswordHashCipher() { - return encryptedPasswordHashCipher; - } - - public byte[] getEncryptedPasswordHash() { - return encryptedPasswordHash; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java b/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java deleted file mode 100644 index aec25ac17..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.Security; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.StreamUtils; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** Simple password based encryption / decryption */ -public class PasswordBasedEncryption { - private final static Log log = LogFactory - .getLog(PasswordBasedEncryption.class); - - static { - Security.addProvider(new BouncyCastleProvider()); - } - - public final static Integer DEFAULT_ITERATION_COUNT = 1024; - /** Stronger with 256, but causes problem with Oracle JVM */ - public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256; - public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128; - public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; - public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; - public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; - public final static String DEFAULT_CHARSET = "UTF-8"; - - private Integer iterationCount = DEFAULT_ITERATION_COUNT; - private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; - private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; - private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; - private String cipherName = DEFAULT_CIPHER_NAME; - - private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, - (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, - (byte) 0x03 }; - private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, - (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, - (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, - (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; - - private Key key; - private Cipher ecipher; - private Cipher dcipher; - - /** - * Default provider is bouncy castle, in order to have consistent behaviour - * across implementations - */ - private String securityProviderName = "BC"; - - /** - * This is up to the caller to clear the passed array. Neither copy of nor - * reference to the passed array is kept - */ - public PasswordBasedEncryption(char[] password) { - this(password, DEFAULT_SALT_8, DEFAULT_IV_16); - } - - /** - * This is up to the caller to clear the passed array. Neither copies of nor - * references to the passed arrays are kept - */ - public PasswordBasedEncryption(char[] password, byte[] passwordSalt, - byte[] initializationVector) { - try { - initKeyAndCiphers(password, passwordSalt, initializationVector); - } catch (InvalidKeyException e) { - Integer previousSecreteKeyLength = secreteKeyLength; - secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED; - log.warn("'" + e.getMessage() + "', will use " + secreteKeyLength - + " secrete key length instead of " - + previousSecreteKeyLength); - try { - initKeyAndCiphers(password, passwordSalt, initializationVector); - } catch (Exception e1) { - throw new ArgeoException( - "Cannot get secret key (with restricted length)", e1); - } - } catch (Exception e) { - throw new ArgeoException("Cannot get secret key", e); - } - } - - protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, - byte[] initializationVector) throws GeneralSecurityException { - byte[] salt = new byte[8]; - System.arraycopy(passwordSalt, 0, salt, 0, salt.length); - // for (int i = 0; i < password.length && i < salt.length; i++) - // salt[i] = (byte) password[i]; - byte[] iv = new byte[16]; - System.arraycopy(initializationVector, 0, iv, 0, iv.length); - - SecretKeyFactory keyFac = SecretKeyFactory - .getInstance(getSecretKeyFactoryName()); - PBEKeySpec keySpec = new PBEKeySpec(password, salt, - getIterationCount(), getKeyLength()); - String secKeyEncryption = getSecretKeyEncryption(); - if (secKeyEncryption != null) { - SecretKey tmp = keyFac.generateSecret(keySpec); - key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption()); - } else { - key = keyFac.generateSecret(keySpec); - } - ecipher = Cipher.getInstance(getCipherName(), securityProviderName); - ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); - dcipher = Cipher.getInstance(getCipherName()); - dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); - } - - public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) - throws IOException { - try { - CipherOutputStream out = new CipherOutputStream(encryptedOut, - ecipher); - StreamUtils.copy(decryptedIn, out); - StreamUtils.closeQuietly(out); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new ArgeoException("Cannot encrypt", e); - } finally { - StreamUtils.closeQuietly(decryptedIn); - } - } - - public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) - throws IOException { - try { - CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, - dcipher); - StreamUtils.copy(decryptedIn, decryptedOut); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new ArgeoException("Cannot decrypt", e); - } finally { - StreamUtils.closeQuietly(encryptedIn); - } - } - - public byte[] encryptString(String str) { - ByteArrayOutputStream out = null; - ByteArrayInputStream in = null; - try { - out = new ByteArrayOutputStream(); - in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET)); - encrypt(in, out); - return out.toByteArray(); - } catch (Exception e) { - throw new ArgeoException("Cannot encrypt", e); - } finally { - StreamUtils.closeQuietly(out); - } - } - - /** Closes the input stream */ - public String decryptAsString(InputStream in) { - ByteArrayOutputStream out = null; - try { - out = new ByteArrayOutputStream(); - decrypt(in, out); - return new String(out.toByteArray(), DEFAULT_CHARSET); - } catch (Exception e) { - throw new ArgeoException("Cannot decrypt", e); - } finally { - StreamUtils.closeQuietly(out); - } - } - - protected Key getKey() { - return key; - } - - protected Cipher getEcipher() { - return ecipher; - } - - protected Cipher getDcipher() { - return dcipher; - } - - protected Integer getIterationCount() { - return iterationCount; - } - - protected Integer getKeyLength() { - return secreteKeyLength; - } - - protected String getSecretKeyFactoryName() { - return secreteKeyFactoryName; - } - - protected String getSecretKeyEncryption() { - return secreteKeyEncryption; - } - - protected String getCipherName() { - return cipherName; - } - - public void setIterationCount(Integer iterationCount) { - this.iterationCount = iterationCount; - } - - public void setSecreteKeyLength(Integer keyLength) { - this.secreteKeyLength = keyLength; - } - - public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { - this.secreteKeyFactoryName = secreteKeyFactoryName; - } - - public void setSecreteKeyEncryption(String secreteKeyEncryption) { - this.secreteKeyEncryption = secreteKeyEncryption; - } - - public void setCipherName(String cipherName) { - this.cipherName = cipherName; - } - - public void setSecurityProviderName(String securityProviderName) { - this.securityProviderName = securityProviderName; - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java deleted file mode 100644 index 1b9f24426..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.io.ByteArrayInputStream; -import java.io.CharArrayReader; -import java.io.InputStream; -import java.io.Reader; -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.ArgeoTypes; -import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.UserJcrUtils; -import org.argeo.security.crypto.AbstractKeyring; -import org.argeo.security.crypto.PBEKeySpecCallback; - -/** JCR based implementation of a keyring */ -public class JcrKeyring extends AbstractKeyring implements ArgeoNames { - /** - * Stronger with 256, but causes problem with Oracle JVM, force 128 in this - * case - */ - public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l; - public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; - public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; - public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; - - private Integer iterationCountFactor = 200; - private Long secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; - private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; - private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; - private String cipherName = DEFAULT_CIPHER_NAME; - - private Session session; - - /** - * When setup is called the session has not yet been saved and we don't want - * to save it since there maybe other data which would be inconsistent. So - * we keep a reference to this node which will then be used (an reset to - * null) when handling the PBE callback. We keep one per thread in case - * multiple users are accessing the same instance of a keyring. - */ - private ThreadLocal notYetSavedKeyring = new ThreadLocal() { - - @Override - protected Node initialValue() { - return null; - } - }; - - @Override - protected Boolean isSetup() { - try { - if (notYetSavedKeyring.get() != null) - return true; - - Node userHome = UserJcrUtils.getUserHome(session); - return userHome.hasNode(ARGEO_KEYRING); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot check whether keyring is setup", e); - } - } - - @Override - protected void setup(char[] password) { - Binary binary = null; - InputStream in = null; - try { - Node userHome = UserJcrUtils.getUserHome(session); - if (userHome.hasNode(ARGEO_KEYRING)) - throw new ArgeoException("Keyring already setup"); - Node keyring = userHome.addNode(ARGEO_KEYRING); - keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC); - - // deterministic salt and iteration count based on username - String username = session.getUserID(); - byte[] salt = new byte[8]; - byte[] usernameBytes = username.getBytes(); - for (int i = 0; i < salt.length; i++) { - if (i < usernameBytes.length) - salt[i] = usernameBytes[i]; - else - salt[i] = 0; - } - in = new ByteArrayInputStream(salt); - binary = session.getValueFactory().createBinary(in); - keyring.setProperty(ARGEO_SALT, binary); - - Integer iterationCount = username.length() * iterationCountFactor; - keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount); - - // default algo - // TODO check if algo and key length are available, use DES if not - keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secreteKeyFactoryName); - keyring.setProperty(ARGEO_KEY_LENGTH, secreteKeyLength); - keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, - secreteKeyEncryption); - keyring.setProperty(ARGEO_CIPHER, cipherName); - - // encrypted password hash - // IOUtils.closeQuietly(in); - // JcrUtils.closeQuietly(binary); - // byte[] btPass = hash(password, salt, iterationCount); - // in = new ByteArrayInputStream(btPass); - // binary = session.getValueFactory().createBinary(in); - // keyring.setProperty(ARGEO_PASSWORD, binary); - - notYetSavedKeyring.set(keyring); - } catch (Exception e) { - throw new ArgeoException("Cannot setup keyring", e); - } finally { - JcrUtils.closeQuietly(binary); - IOUtils.closeQuietly(in); - // JcrUtils.discardQuietly(session); - } - } - - @Override - protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) { - try { - Node userHome = UserJcrUtils.getUserHome(session); - Node keyring; - if (userHome.hasNode(ARGEO_KEYRING)) - keyring = userHome.getNode(ARGEO_KEYRING); - else if (notYetSavedKeyring.get() != null) - keyring = notYetSavedKeyring.get(); - else - throw new ArgeoException("Keyring not setup"); - - pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY) - .getString(), JcrUtils.getBinaryAsBytes(keyring - .getProperty(ARGEO_SALT)), - (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(), - (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(), - keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION) - .getString()); - - if (notYetSavedKeyring.get() != null) - notYetSavedKeyring.remove(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot handle key spec callback", e); - } - } - - /** The parent node must already exist at this path. */ - @Override - protected synchronized void encrypt(String path, InputStream unencrypted) { - // should be called first for lazy initialization - SecretKey secretKey = getSecretKey(); - - Binary binary = null; - InputStream in = null; - try { - Cipher cipher = createCipher(); - Node node; - if (!session.nodeExists(path)) { - String parentPath = JcrUtils.parentPath(path); - if (!session.nodeExists(parentPath)) - throw new ArgeoException("No parent node of " + path); - Node parentNode = session.getNode(parentPath); - node = parentNode.addNode(JcrUtils.nodeNameFromPath(path)); - } else { - node = session.getNode(path); - } - node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); - SecureRandom random = new SecureRandom(); - byte[] iv = new byte[16]; - random.nextBytes(iv); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); - - in = new CipherInputStream(unencrypted, cipher); - binary = session.getValueFactory().createBinary(in); - node.setProperty(Property.JCR_DATA, binary); - session.save(); - } catch (Exception e) { - throw new ArgeoException("Cannot encrypt", e); - } finally { - IOUtils.closeQuietly(unencrypted); - IOUtils.closeQuietly(in); - JcrUtils.closeQuietly(binary); - } - } - - @Override - protected synchronized InputStream decrypt(String path) { - Binary binary = null; - InputStream encrypted = null; - Reader reader = null; - try { - if (!session.nodeExists(path)) { - char[] password = ask(); - reader = new CharArrayReader(password); - return new ByteArrayInputStream(IOUtils.toByteArray(reader)); - } else { - // should be called first for lazy initialisation - SecretKey secretKey = getSecretKey(); - - Cipher cipher = createCipher(); - - Node node = session.getNode(path); - if (node.hasProperty(ARGEO_IV)) { - byte[] iv = JcrUtils.getBinaryAsBytes(node - .getProperty(ARGEO_IV)); - cipher.init(Cipher.DECRYPT_MODE, secretKey, - new IvParameterSpec(iv)); - } else { - cipher.init(Cipher.DECRYPT_MODE, secretKey); - } - - binary = node.getProperty(Property.JCR_DATA).getBinary(); - encrypted = binary.getStream(); - return new CipherInputStream(encrypted, cipher); - } - } catch (Exception e) { - throw new ArgeoException("Cannot decrypt", e); - } finally { - IOUtils.closeQuietly(encrypted); - IOUtils.closeQuietly(reader); - JcrUtils.closeQuietly(binary); - } - } - - protected Cipher createCipher() { - try { - Node userHome = UserJcrUtils.getUserHome(session); - if (!userHome.hasNode(ARGEO_KEYRING)) - throw new ArgeoException("Keyring not setup"); - Node keyring = userHome.getNode(ARGEO_KEYRING); - Cipher cipher = Cipher.getInstance(keyring - .getProperty(ARGEO_CIPHER).getString(), - getSecurityProvider()); - return cipher; - } catch (Exception e) { - throw new ArgeoException("Cannot get cipher", e); - } - } - - public synchronized void changePassword(char[] oldPassword, - char[] newPassword) { - // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted - } - - public synchronized void setSession(Session session) { - this.session = session; - } - - public void setIterationCountFactor(Integer iterationCountFactor) { - this.iterationCountFactor = iterationCountFactor; - } - - public void setSecreteKeyLength(Long keyLength) { - this.secreteKeyLength = keyLength; - } - - public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { - this.secreteKeyFactoryName = secreteKeyFactoryName; - } - - public void setSecreteKeyEncryption(String secreteKeyEncryption) { - this.secreteKeyEncryption = secreteKeyEncryption; - } - - public void setCipherName(String cipherName) { - this.cipherName = cipherName; - } - -} \ No newline at end of file diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java deleted file mode 100644 index e9ab89c2a..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrSecurityModel.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Session; - -/** - * Manages data expected by the Argeo security model, such as user home and - * profile. - */ -public interface JcrSecurityModel { - /** - * To be called before user details are loaded. Make sure than any logged in - * user has a home directory with full access and a profile with information - * about him (read access) - * - * @return the user profile (whose parent is the user home), never null - */ - public Node sync(Session session, String username, List roles); -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java deleted file mode 100644 index 2f7b97b78..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrUserDetails.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.UserJcrUtils; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.DisabledException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.LockedException; -import org.springframework.security.userdetails.User; - -/** User details based on a user profile node. */ -public class JcrUserDetails extends User implements ArgeoNames { - private static final long serialVersionUID = -8142764995842559646L; - private final String homePath; - private final String securityWorkspace; - - /** Human readable user name */ - private String displayName; - - protected JcrUserDetails(String securityWorkspace, String homePath, - String username, String password, boolean enabled, - boolean accountNonExpired, boolean credentialsNonExpired, - boolean accountNonLocked, GrantedAuthority[] authorities) - throws IllegalArgumentException { - super(username, password, enabled, accountNonExpired, - credentialsNonExpired, accountNonLocked, authorities); - this.homePath = homePath; - this.securityWorkspace = securityWorkspace; - } - - public JcrUserDetails(Node userProfile, String password, - GrantedAuthority[] authorities) throws RepositoryException { - super( - userProfile.getProperty(ARGEO_USER_ID).getString(), - password, - userProfile.getProperty(ARGEO_ENABLED).getBoolean(), - userProfile.getProperty(ARGEO_ACCOUNT_NON_EXPIRED).getBoolean(), - userProfile.getProperty(ARGEO_CREDENTIALS_NON_EXPIRED) - .getBoolean(), userProfile.getProperty( - ARGEO_ACCOUNT_NON_LOCKED).getBoolean(), authorities); - // human readable name - if (userProfile.hasProperty(Property.JCR_TITLE)) { - displayName = userProfile.getProperty(Property.JCR_TITLE) - .getString(); - if (displayName.trim().equals("")) - displayName = null; - } - if (displayName == null) - displayName = userProfile.getProperty(ARGEO_USER_ID).getString(); - // home is defined as the parent of the profile - homePath = userProfile.getParent().getPath(); - securityWorkspace = userProfile.getSession().getWorkspace().getName(); - } - - /** - * Convenience constructor - * - * @param session - * the security session - * @param username - * the username - * @param password - * the password, can be null - * @param authorities - * the granted authorities - */ - public JcrUserDetails(Session session, String username, String password, - GrantedAuthority[] authorities) throws RepositoryException { - this(UserJcrUtils.getUserProfile(session, username), - password != null ? password : "", authorities); - } - - /** - * Check the account status in JCR, throwing the exceptions expected by - * Spring security if needed. - */ - public static void checkAccountStatus(Node userProfile) { - try { - if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) - throw new DisabledException(userProfile.getPath() - + " is disabled"); - if (!userProfile.getProperty(ARGEO_ACCOUNT_NON_LOCKED).getBoolean()) - throw new LockedException(userProfile.getPath() + " is locked"); - } catch (RepositoryException e) { - throw new BadCredentialsException("Cannot check account status", e); - } - } - - /** Clone immutable with new roles */ - public JcrUserDetails cloneWithNewRoles(List roles) { - List authorities = new ArrayList(); - for (String role : roles) { - authorities.add(new GrantedAuthorityImpl(role)); - } - return new JcrUserDetails(securityWorkspace, homePath, getUsername(), - getPassword(), isEnabled(), isAccountNonExpired(), - isAccountNonExpired(), isAccountNonLocked(), - authorities.toArray(new GrantedAuthority[authorities.size()])); - } - - /** Clone immutable with new password */ - public JcrUserDetails cloneWithNewPassword(String password) { - return new JcrUserDetails(securityWorkspace, homePath, getUsername(), - password, isEnabled(), isAccountNonExpired(), - isAccountNonExpired(), isAccountNonLocked(), getAuthorities()); - } - - public String getHomePath() { - return homePath; - } - - /** Not yet API */ - public String getSecurityWorkspace() { - return securityWorkspace; - } - - /** The human readable name of this user */ - public String getDisplayName() { - return displayName; - } - - @Override - public String toString() { - return getDisplayName(); - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java deleted file mode 100644 index aa95e322d..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrAuthenticationProvider.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; -import org.argeo.security.OsAuthenticationToken; -import org.argeo.security.SecurityUtils; -import org.argeo.security.core.OsAuthenticationProvider; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationException; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; -import org.springframework.security.userdetails.UserDetails; - -/** Relies on OS to authenticate and additionally setup JCR */ -public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { - private Repository repository; - private Session nodeSession; - - private UserDetails userDetails; - private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel(); - - private final static String JVM_OSUSER = System.getProperty("user.name"); - - public void init() { - try { - nodeSession = repository.login(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot initialize", e); - } - } - - public void destroy() { - JcrUtils.logoutQuietly(nodeSession); - } - - public Authentication authenticate(Authentication authentication) - throws AuthenticationException { - if (authentication instanceof UsernamePasswordAuthenticationToken) { - // deal with remote access to internal server - // FIXME very primitive and unsecure at this sSession adminSession - // =tage - // consider using the keyring for username / password authentication - // or certificate - UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; - if (!upat.getPrincipal().toString().equals(JVM_OSUSER)) - throw new BadCredentialsException("Wrong credentials"); - UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken( - authentication.getPrincipal(), - authentication.getCredentials(), getBaseAuthorities()); - authen.setDetails(userDetails); - return authen; - } else if (authentication instanceof OsAuthenticationToken) { - OsAuthenticationToken authen = (OsAuthenticationToken) super - .authenticate(authentication); - try { - // WARNING: at this stage we assume that the java properties - // will have the same value - GrantedAuthority[] authorities = getBaseAuthorities(); - String username = JVM_OSUSER; - Node userProfile = jcrSecurityModel.sync(nodeSession, username, - SecurityUtils.authoritiesToStringList(authorities)); - JcrUserDetails.checkAccountStatus(userProfile); - - userDetails = new JcrUserDetails(userProfile, authen - .getCredentials().toString(), authorities); - authen.setDetails(userDetails); - return authen; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(nodeSession); - throw new ArgeoException( - "Unexpected exception when synchronizing OS and JCR security ", - e); - } - } else { - throw new ArgeoException("Unsupported authentication " - + authentication.getClass()); - } - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { - this.jcrSecurityModel = jcrSecurityModel; - } - - @SuppressWarnings("rawtypes") - public boolean supports(Class authentication) { - return OsAuthenticationToken.class.isAssignableFrom(authentication) - || UsernamePasswordAuthenticationToken.class - .isAssignableFrom(authentication); - } -} \ No newline at end of file diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java deleted file mode 100644 index c25bdb865..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/OsJcrUserAdminService.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.UserJcrUtils; -import org.argeo.security.UserAdminService; -import org.springframework.dao.DataAccessException; -import org.springframework.security.userdetails.User; -import org.springframework.security.userdetails.UserDetails; -import org.springframework.security.userdetails.UsernameNotFoundException; - -/** - * Dummy user service to be used when running as a single OS user (typically - * desktop). TODO integrate with JCR user / groups - */ -public class OsJcrUserAdminService implements UserAdminService { - private Repository repository; - - /** In memory roles provided by applications. */ - private List roles = new ArrayList(); - - // private Session adminSession; - - public void init() { - // try { - // adminSession = repository.login(); - // } catch (RepositoryException e) { - // throw new ArgeoException("Cannot initialize", e); - // } - } - - public void destroy() { - // JcrUtils.logoutQuietly(adminSession); - } - - /** Unsupported */ - public void createUser(UserDetails user) { - throw new UnsupportedOperationException(); - } - - /** Does nothing */ - public void updateUser(UserDetails user) { - - } - - /** Unsupported */ - public void deleteUser(String username) { - throw new UnsupportedOperationException(); - } - - /** Unsupported */ - public void changePassword(String oldPassword, String newPassword) { - throw new UnsupportedOperationException(); - } - - public boolean userExists(String username) { - if (getSPropertyUsername().equals(username)) - return true; - else - return false; - } - - public UserDetails loadUserByUsername(String username) - throws UsernameNotFoundException, DataAccessException { - if (getSPropertyUsername().equals(username)) { - UserDetails userDetails; - if (repository != null) { - Session adminSession = null; - try { - adminSession = repository.login(); - Node userProfile = UserJcrUtils.getUserProfile( - adminSession, username); - userDetails = new JcrUserDetails(userProfile, "", - OsJcrAuthenticationProvider.getBaseAuthorities()); - } catch (RepositoryException e) { - throw new ArgeoException( - "Cannot retrieve user profile for " + username, e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } else { - userDetails = new User(username, "", true, true, true, true, - OsJcrAuthenticationProvider.getBaseAuthorities()); - } - return userDetails; - } else { - throw new UnsupportedOperationException(); - } - } - - protected final String getSPropertyUsername() { - return System.getProperty("user.name"); - } - - public Set listUsers() { - Set set = new HashSet(); - set.add(getSPropertyUsername()); - return set; - } - - public Set listUsersInRole(String role) { - Set set = new HashSet(); - set.add(getSPropertyUsername()); - return set; - } - - /** Does nothing */ - public void synchronize() { - } - - /** Unsupported */ - public void newRole(String role) { - roles.add(role); - } - - public Set listEditableRoles() { - return new HashSet(roles); - } - - /** Unsupported */ - public void deleteRole(String role) { - roles.remove(role); - } - - public void setRepository(Repository repository) { - this.repository = repository; - } -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java deleted file mode 100644 index 87208b2a3..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; -import javax.jcr.Value; - -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.UserJcrUtils; -import org.argeo.security.NodeAuthenticationToken; -import org.osgi.framework.BundleContext; -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationException; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.GrantedAuthority; -import org.springframework.security.GrantedAuthorityImpl; -import org.springframework.security.providers.AuthenticationProvider; - -/** Connects to a JCR repository and delegates authentication to it. */ -public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, - ArgeoNames { - private RepositoryFactory repositoryFactory; - private BundleContext bundleContext; - - public final static String ROLE_REMOTE = "ROLE_REMOTE"; - - public Authentication authenticate(Authentication authentication) - throws AuthenticationException { - NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication; - String url = siteAuth.getUrl(); - if (url == null)// TODO? login on own node - throw new ArgeoException("No url set in " + siteAuth); - Session session; - - Node userProfile; - try { - SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), - siteAuth.getCredentials().toString().toCharArray()); - // get repository - Repository repository = new RemoteJcrRepositoryWrapper( - repositoryFactory, url, sp); - if (bundleContext != null) { - Dictionary serviceProperties = new Hashtable(); - serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, - ArgeoJcrConstants.ALIAS_NODE); - serviceProperties - .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); - bundleContext.registerService(Repository.class.getName(), - repository, serviceProperties); - } - // Repository repository = ArgeoJcrUtils.getRepositoryByUri( - // repositoryFactory, url); - // if (repository == null) - // throw new ArgeoException("Cannot connect to " + url); - - session = repository.login(sp, null); - - userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID()); - JcrUserDetails.checkAccountStatus(userProfile); - - // Node userHome = UserJcrUtils.getUserHome(session); - // if (userHome == null || - // !userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) - // throw new ArgeoException("No profile for user " - // + siteAuth.getName() + " in security workspace " - // + siteAuth.getSecurityWorkspace() + " of " - // + siteAuth.getUrl()); - // userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); - } catch (RepositoryException e) { - throw new BadCredentialsException( - "Cannot authenticate " + siteAuth, e); - } - - try { - // Node userHome = UserJcrUtils.getUserHome(session); - // retrieve remote roles - List authoritiesList = new ArrayList(); - if (userProfile != null - && userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { - Value[] roles = userProfile.getProperty( - ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); - for (int i = 0; i < roles.length; i++) - authoritiesList.add(new GrantedAuthorityImpl(roles[i] - .getString())); - } - authoritiesList.add(new GrantedAuthorityImpl(ROLE_REMOTE)); - - // create authenticated objects - GrantedAuthority[] authorities = authoritiesList - .toArray(new GrantedAuthority[authoritiesList.size()]); - JcrUserDetails userDetails = new JcrUserDetails(userProfile, - siteAuth.getCredentials().toString(), authorities); - NodeAuthenticationToken authenticated = new NodeAuthenticationToken( - siteAuth, authorities); - authenticated.setDetails(userDetails); - return authenticated; - } catch (RepositoryException e) { - throw new ArgeoException( - "Unexpected exception when authenticating to " + url, e); - } - } - - @SuppressWarnings("rawtypes") - public boolean supports(Class authentication) { - return NodeAuthenticationToken.class.isAssignableFrom(authentication); - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java deleted file mode 100644 index f0ad3a3a9..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import javax.jcr.Credentials; -import javax.jcr.LoginException; -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoJcrUtils; -import org.argeo.jcr.JcrRepositoryWrapper; -import org.argeo.security.NodeAuthenticationToken; -import org.argeo.security.SystemAuthentication; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; - -/** - * Wrapper around a remote Jackrabbit repository which allows to simplify - * configuration and intercept some actions. It exposes itself as a - * {@link Repository}. - */ -public class RemoteJcrRepositoryWrapper extends JcrRepositoryWrapper { - private final static Log log = LogFactory - .getLog(RemoteJcrRepositoryWrapper.class); - - private String uri = null; - - private RepositoryFactory repositoryFactory; - - // remote - private Credentials remoteSystemCredentials = null; - - /** - * Empty constructor, {@link #init()} should be called after properties have - * been set - */ - public RemoteJcrRepositoryWrapper() { - } - - /** - * Embedded constructor, calling the {@link #init()} method. - * - * @param alias - * if not null the repository will be published under this alias - */ - public RemoteJcrRepositoryWrapper(RepositoryFactory repositoryFactory, - String uri, Credentials remoteSystemCredentials) { - this.repositoryFactory = repositoryFactory; - this.uri = uri; - this.remoteSystemCredentials = remoteSystemCredentials; - init(); - } - - public void init() { - Repository repository = createJackrabbitRepository(); - setRepository(repository); - } - - /** Actually creates the new repository. */ - protected Repository createJackrabbitRepository() { - long begin = System.currentTimeMillis(); - try { - if (uri == null || uri.trim().equals("")) - throw new ArgeoException("Remote URI not set"); - - Repository repository = ArgeoJcrUtils.getRepositoryByUri( - repositoryFactory, uri); - if (repository == null) - throw new ArgeoException("Remote JCR repository " + uri - + " not found"); - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Created remote JCR repository in " + duration - + " s from URI " + uri); - // we assume that the data model of the remote repository has - // been properly initialized - return repository; - } catch (Exception e) { - throw new ArgeoException("Cannot create remote JCR repository " - + uri, e); - } - } - - /** Shutdown the repository */ - public void destroy() throws Exception { - super.destroy(); - } - - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, - RepositoryException { - - // retrieve credentials for remote - if (credentials == null) { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); - if (authentication != null) { - if (authentication instanceof UsernamePasswordAuthenticationToken) { - UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; - credentials = new SimpleCredentials(upat.getName(), upat - .getCredentials().toString().toCharArray()); - } else if ((authentication instanceof SystemAuthentication) - || (authentication instanceof NodeAuthenticationToken)) { - credentials = remoteSystemCredentials; - } - } - } - - return super.login(credentials, workspaceName); - } - - public void setUri(String uri) { - this.uri = uri; - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) { - this.remoteSystemCredentials = remoteSystemCredentials; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java deleted file mode 100644 index b39877420..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SecureThreadBoundSession.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import javax.jcr.Session; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.jcr.spring.ThreadBoundSession; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContextHolder; - -/** - * Thread bounded JCR session factory which checks authentication and is - * autoconfigured in Spring. - */ -public class SecureThreadBoundSession extends ThreadBoundSession { - private final static Log log = LogFactory - .getLog(SecureThreadBoundSession.class); - - @Override - protected Session preCall(Session session) { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); - if (authentication != null) { - String userID = session.getUserID(); - String currentUserName = authentication.getName(); - if (currentUserName != null) { - if (!userID.equals(currentUserName)) { - log.warn("Current session has user ID " + userID - + " while logged is user is " + currentUserName - + "(authentication=" + authentication + ")" - + ". Re-login."); - // TODO throw an exception - return login(); - } - } - } - return super.preCall(session); - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java deleted file mode 100644 index fc0158738..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/SimpleJcrSecurityModel.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr; - -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.security.Privilege; -import javax.jcr.version.VersionManager; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoJcrConstants; -import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.ArgeoTypes; -import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.UserJcrUtils; - -/** - * Manages data expected by the Argeo security model, such as user home and - * profile. - */ -public class SimpleJcrSecurityModel implements JcrSecurityModel { - private final static Log log = LogFactory - .getLog(SimpleJcrSecurityModel.class); - // ArgeoNames not implemented as interface in order to ease derivation by - // Jackrabbit bundles - - /** The home base path. */ - private String homeBasePath = "/home"; - - public synchronized Node sync(Session session, String username, - List roles) { - // TODO check user name validity (e.g. should not start by ROLE_) - - try { - Node userHome = UserJcrUtils.getUserHome(session, username); - if (userHome == null) { - String homePath = generateUserPath(homeBasePath, username); - userHome = JcrUtils.mkdirs(session, homePath); - // userHome = JcrUtils.mkfolders(session, homePath); - userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - - JcrUtils.clearAccessControList(session, homePath, username); - JcrUtils.addPrivilege(session, homePath, username, - Privilege.JCR_ALL); - } else { - // for backward compatibility with pre 1.0 security model - if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { - userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove(); - userHome.getSession().save(); - } - } - - // Remote roles - if (roles != null) { - // writeRemoteRoles(userHome, roles); - } - - Node userProfile = UserJcrUtils.getUserProfile(session, username); - if (userProfile == null) { - String personPath = generateUserPath( - ArgeoJcrConstants.PEOPLE_BASE_PATH, username); - Node personBase = JcrUtils.mkdirs(session, personPath); - userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, - true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, - true); - userProfile.setProperty( - ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); - session.save(); - - JcrUtils.clearAccessControList(session, userProfile.getPath(), - username); - JcrUtils.addPrivilege(session, userProfile.getPath(), username, - Privilege.JCR_READ); - - VersionManager versionManager = session.getWorkspace() - .getVersionManager(); - if (versionManager.isCheckedOut(userProfile.getPath())) - versionManager.checkin(userProfile.getPath()); - - } - - // Remote roles - if (roles != null) { - writeRemoteRoles(userProfile, roles); - } - return userProfile; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot sync node security model for " - + username, e); - } - } - - /** Generate path for a new user home */ - protected String generateUserPath(String base, String username) { - int atIndex = username.indexOf('@'); - if (atIndex > 0) { - String domain = username.substring(0, atIndex); - String name = username.substring(atIndex + 1); - return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' - + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/' - + name; - } else if (atIndex == 0 || atIndex == (username.length() - 1)) { - throw new ArgeoException("Unsupported username " + username); - } else { - return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/' - + username; - } - } - - /** Write remote roles used by remote access in the home directory */ - protected void writeRemoteRoles(Node userHome, List roles) - throws RepositoryException { - boolean writeRoles = false; - if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { - Value[] remoteRoles = userHome.getProperty( - ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); - if (remoteRoles.length != roles.size()) - writeRoles = true; - else - for (int i = 0; i < remoteRoles.length; i++) - if (!remoteRoles[i].getString().equals(roles.get(i))) - writeRoles = true; - } else - writeRoles = true; - - if (writeRoles) { - userHome.getSession().getWorkspace().getVersionManager() - .checkout(userHome.getPath()); - String[] roleIds = roles.toArray(new String[roles.size()]); - userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds); - JcrUtils.updateLastModified(userHome); - userHome.getSession().save(); - userHome.getSession().getWorkspace().getVersionManager() - .checkin(userHome.getPath()); - if (log.isDebugEnabled()) - log.debug("Wrote remote roles " + roles + " for " - + userHome.getProperty(ArgeoNames.ARGEO_USER_ID)); - } - - } - - public void setHomeBasePath(String homeBasePath) { - this.homeBasePath = homeBasePath; - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java b/org.argeo.security.core/src/main/java/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java deleted file mode 100644 index 37dc98676..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.jcr.rememberme; - -import java.util.Date; - -import org.springframework.security.ui.rememberme.PersistentRememberMeToken; -import org.springframework.security.ui.rememberme.PersistentTokenRepository; - -public class JcrPersistentTokenRepository implements PersistentTokenRepository { - - public void createNewToken(PersistentRememberMeToken token) { - // TODO Auto-generated method stub - - } - - public void updateToken(String series, String tokenValue, Date lastUsed) { - // TODO Auto-generated method stub - - } - - public PersistentRememberMeToken getTokenForSeries(String seriesId) { - // TODO Auto-generated method stub - return null; - } - - public void removeUserTokens(String username) { - // TODO Auto-generated method stub - - } - -} diff --git a/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java b/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java deleted file mode 100644 index 1da985703..000000000 --- a/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.log4j; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -import org.apache.log4j.AppenderSkeleton; -import org.apache.log4j.Level; -import org.apache.log4j.LogManager; -import org.apache.log4j.Logger; -import org.apache.log4j.PropertyConfigurator; -import org.apache.log4j.spi.LoggingEvent; -import org.argeo.ArgeoException; -import org.argeo.ArgeoLogListener; -import org.argeo.ArgeoLogger; -import org.argeo.security.SecurityUtils; - -/** Not meant to be used directly in standard log4j config */ -public class SecureLogger implements ArgeoLogger { - - private Boolean disabled = false; - - private String level = null; - - private Level log4jLevel = null; - // private Layout layout; - - private Properties configuration; - - private AppenderImpl appender; - - private final List everythingListeners = Collections - .synchronizedList(new ArrayList()); - private final List allUsersListeners = Collections - .synchronizedList(new ArrayList()); - private final Map> userListeners = Collections - .synchronizedMap(new HashMap>()); - - private BlockingQueue events; - private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - - private Integer maxLastEventsCount = 10 * 1000; - - /** Marker to prevent stack overflow */ - private ThreadLocal dispatching = new ThreadLocal() { - - @Override - protected Boolean initialValue() { - return false; - } - }; - - public void init() { - try { - events = new LinkedBlockingQueue(); - - // if (layout != null) - // setLayout(layout); - // else - // setLayout(new PatternLayout(pattern)); - appender = new AppenderImpl(); - reloadConfiguration(); - Logger.getRootLogger().addAppender(appender); - - logDispatcherThread = new LogDispatcherThread(); - logDispatcherThread.start(); - } catch (Exception e) { - throw new ArgeoException("Cannot initialize log4j"); - } - } - - public void destroy() throws Exception { - Logger.getRootLogger().removeAppender(appender); - allUsersListeners.clear(); - for (List lst : userListeners.values()) - lst.clear(); - userListeners.clear(); - - events.clear(); - events = null; - logDispatcherThread.interrupt(); - } - - // public void setLayout(Layout layout) { - // this.layout = layout; - // } - - public synchronized void register(ArgeoLogListener listener, - Integer numberOfPreviousEvents) { - String username = SecurityUtils.getCurrentThreadUsername(); - if (username == null) - throw new ArgeoException( - "Only authenticated users can register a log listener"); - - if (!userListeners.containsKey(username)) { - List lst = Collections - .synchronizedList(new ArrayList()); - userListeners.put(username, lst); - } - userListeners.get(username).add(listener); - List lastEvents = logDispatcherThread.getLastEvents(username, - numberOfPreviousEvents); - for (LogEvent evt : lastEvents) - dispatchEvent(listener, evt); - } - - public synchronized void registerForAll(ArgeoLogListener listener, - Integer numberOfPreviousEvents, boolean everything) { - if (everything) - everythingListeners.add(listener); - else - allUsersListeners.add(listener); - List lastEvents = logDispatcherThread.getLastEvents(null, - numberOfPreviousEvents); - for (LogEvent evt : lastEvents) - if (everything || evt.getUsername() != null) - dispatchEvent(listener, evt); - } - - public synchronized void unregister(ArgeoLogListener listener) { - String username = SecurityUtils.getCurrentThreadUsername(); - if (!userListeners.containsKey(username)) - throw new ArgeoException("No user listeners " + listener - + " registered for user " + username); - if (!userListeners.get(username).contains(listener)) - throw new ArgeoException("No user listeners " + listener - + " registered for user " + username); - userListeners.get(username).remove(listener); - if (userListeners.get(username).isEmpty()) - userListeners.remove(username); - - } - - public synchronized void unregisterForAll(ArgeoLogListener listener) { - everythingListeners.remove(listener); - allUsersListeners.remove(listener); - } - - /** For development purpose, since using regular logging is not easy here */ - static void stdOut(Object obj) { - System.out.println(obj); - } - - // public void setPattern(String pattern) { - // this.pattern = pattern; - // } - - public void setDisabled(Boolean disabled) { - this.disabled = disabled; - } - - public void setLevel(String level) { - this.level = level; - } - - public void setConfiguration(Properties configuration) { - this.configuration = configuration; - } - - public void updateConfiguration(Properties configuration) { - setConfiguration(configuration); - reloadConfiguration(); - } - - public Properties getConfiguration() { - return configuration; - } - - /** Reloads configuration (if the configuration {@link Properties} is set) */ - protected void reloadConfiguration() { - if (configuration != null) { - LogManager.resetConfiguration(); - PropertyConfigurator.configure(configuration); - } - } - - protected synchronized void processLoggingEvent(LogEvent event) { - if (disabled) - return; - - if (dispatching.get()) - return; - - if (level != null && !level.trim().equals("")) { - if (log4jLevel == null || !log4jLevel.toString().equals(level)) - try { - log4jLevel = Level.toLevel(level); - } catch (Exception e) { - System.err - .println("Log4j level could not be set for level '" - + level + "', resetting it to null."); - e.printStackTrace(); - level = null; - } - - if (log4jLevel != null - && !event.getLoggingEvent().getLevel() - .isGreaterOrEqual(log4jLevel)) { - return; - } - } - - try { - // admin listeners - Iterator everythingIt = everythingListeners - .iterator(); - while (everythingIt.hasNext()) - dispatchEvent(everythingIt.next(), event); - - if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners - .iterator(); - while (allUsersIt.hasNext()) - dispatchEvent(allUsersIt.next(), event); - - if (userListeners.containsKey(event.getUsername())) { - Iterator userIt = userListeners.get( - event.getUsername()).iterator(); - while (userIt.hasNext()) - dispatchEvent(userIt.next(), event); - } - } - } catch (Exception e) { - stdOut("Cannot process logging event"); - e.printStackTrace(); - } - } - - protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { - LoggingEvent event = evt.getLoggingEvent(); - logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event - .getLevel().toString(), event.getLoggerName(), event - .getThreadName(), event.getMessage(), event - .getThrowableStrRep()); - } - - private class AppenderImpl extends AppenderSkeleton { - public boolean requiresLayout() { - return false; - } - - public void close() { - } - - @Override - protected void append(LoggingEvent event) { - if (events != null) { - try { - String username = SecurityUtils.getCurrentThreadUsername(); - events.put(new LogEvent(username, event)); - } catch (InterruptedException e) { - // silent - } - } - } - - } - - private class LogDispatcherThread extends Thread { - /** encapsulated in order to simplify concurrency management */ - private LinkedList lastEvents = new LinkedList(); - - public LogDispatcherThread() { - super("Argeo Logging Dispatcher Thread"); - } - - public void run() { - while (events != null) { - try { - LogEvent loggingEvent = events.take(); - processLoggingEvent(loggingEvent); - addLastEvent(loggingEvent); - } catch (InterruptedException e) { - if (events == null) - return; - } - } - } - - protected synchronized void addLastEvent(LogEvent loggingEvent) { - if (lastEvents.size() >= maxLastEventsCount) - lastEvents.poll(); - lastEvents.add(loggingEvent); - } - - public synchronized List getLastEvents(String username, - Integer maxCount) { - LinkedList evts = new LinkedList(); - ListIterator it = lastEvents.listIterator(lastEvents - .size()); - int count = 0; - while (it.hasPrevious() && (count < maxCount)) { - LogEvent evt = it.previous(); - if (username == null || username.equals(evt.getUsername())) { - evts.push(evt); - count++; - } - } - return evts; - } - } - - private class LogEvent { - private final String username; - private final LoggingEvent loggingEvent; - - public LogEvent(String username, LoggingEvent loggingEvent) { - super(); - this.username = username; - this.loggingEvent = loggingEvent; - } - - @Override - public int hashCode() { - return loggingEvent.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return loggingEvent.equals(obj); - } - - @Override - public String toString() { - return username + "@ " + loggingEvent.toString(); - } - - public String getUsername() { - return username; - } - - public LoggingEvent getLoggingEvent() { - return loggingEvent; - } - - } -} diff --git a/org.argeo.security.core/src/org/argeo/security/NodeAuthenticationToken.java b/org.argeo.security.core/src/org/argeo/security/NodeAuthenticationToken.java new file mode 100644 index 000000000..1870675d8 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/NodeAuthenticationToken.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +/** Credentials required for the authentication to a node. */ +public class NodeAuthenticationToken extends + UsernamePasswordAuthenticationToken { + private static final long serialVersionUID = 1955222132884795213L; + private final String url; + + /** Non authenticated local constructor */ + public NodeAuthenticationToken(Object principal, Object credentials) { + super(principal, credentials); + this.url = null; + } + + /** Non authenticated remote constructor */ + public NodeAuthenticationToken(Object principal, Object credentials, + String url) { + super(principal, credentials); + this.url = url; + } + + /** Authenticated constructor */ + public NodeAuthenticationToken(NodeAuthenticationToken sat, + GrantedAuthority[] authorities) { + super(sat.getPrincipal(), sat.getCredentials(), authorities); + this.url = sat.getUrl(); + } + + public String getUrl() { + return url; + } + + public Boolean isRemote() { + return url != null; + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/OsAuthenticationToken.java b/org.argeo.security.core/src/org/argeo/security/OsAuthenticationToken.java new file mode 100644 index 000000000..b3727b26f --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/OsAuthenticationToken.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import java.security.AccessController; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.argeo.ArgeoException; +import org.argeo.OperatingSystem; +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.userdetails.UserDetails; + +/** Abstracts principals provided by com.sun.security.auth.module login modules. */ +public class OsAuthenticationToken implements Authentication { + private static final long serialVersionUID = -7544626794250917244L; + + final Class osUserPrincipalClass; + final Class osUserIdPrincipalClass; + final Class osGroupIdPrincipalClass; + + private List grantedAuthorities; + + private UserDetails details; + + /** Request */ + public OsAuthenticationToken(GrantedAuthority[] grantedAuthorities) { + this.grantedAuthorities = grantedAuthorities != null ? Arrays + .asList(grantedAuthorities) : null; + ClassLoader cl = getClass().getClassLoader(); + switch (OperatingSystem.os) { + case OperatingSystem.WINDOWS: + osUserPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.NTUserPrincipal"); + osUserIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.NTSidUserPrincipal"); + osGroupIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.NTSidGroupPrincipal"); + break; + case OperatingSystem.NIX: + osUserPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.UnixPrincipal"); + osUserIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.UnixNumericUserPrincipal"); + osGroupIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.UnixNumericGroupPrincipal"); + break; + case OperatingSystem.SOLARIS: + osUserPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.SolarisPrincipal"); + osUserIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.SolarisNumericUserPrincipal"); + osGroupIdPrincipalClass = getPrincipalClass(cl, + "com.sun.security.auth.SolarisNumericGroupPrincipal"); + break; + + default: + throw new ArgeoException("Unsupported operating system " + + OperatingSystem.os); + } + + } + + /** Authenticated */ + public OsAuthenticationToken() { + this(null); + } + + /** @return the name, or null if not yet logged */ + public String getName() { + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject == null) + return null; + return getUser().getName(); + } + + /** + * Should not be called during authentication since group IDs are not yet + * available {@link Subject} has been set + */ + public GrantedAuthority[] getAuthorities() { + // grantedAuthorities should not be null at this stage + List gas = new ArrayList( + grantedAuthorities); + for (Principal groupPrincipal : getGroupsIds()) { + gas.add(new GrantedAuthorityImpl("OSGROUP_" + + groupPrincipal.getName())); + } + return gas.toArray(new GrantedAuthority[gas.size()]); + } + + public UserDetails getDetails() { + return details; + } + + public void setDetails(UserDetails details) { + this.details = details; + } + + public boolean isAuthenticated() { + return grantedAuthorities != null; + } + + public void setAuthenticated(boolean isAuthenticated) + throws IllegalArgumentException { + if (grantedAuthorities != null) + grantedAuthorities.clear(); + grantedAuthorities = null; + } + + @SuppressWarnings("unchecked") + protected static Class getPrincipalClass( + ClassLoader cl, String className) { + try { + return (Class) cl.loadClass(className); + } catch (ClassNotFoundException e) { + throw new ArgeoException("Cannot load principal class", e); + } + } + + public Object getPrincipal() { + return getUser(); + } + + public Principal getUser() { + Subject subject = getSubject(); + Set userPrincipals = subject + .getPrincipals(osUserPrincipalClass); + if (userPrincipals == null || userPrincipals.size() == 0) + throw new ArgeoException("No OS principal"); + if (userPrincipals.size() > 1) + throw new ArgeoException("More than one OS principal"); + Principal user = userPrincipals.iterator().next(); + return user; + } + + public Principal getUserId() { + Subject subject = getSubject(); + Set userIdsPrincipals = subject + .getPrincipals(osUserIdPrincipalClass); + if (userIdsPrincipals == null || userIdsPrincipals.size() == 0) + throw new ArgeoException("No user id principal"); + if (userIdsPrincipals.size() > 1) + throw new ArgeoException("More than one user id principal"); + Principal userId = userIdsPrincipals.iterator().next(); + return userId; + } + + public Set getGroupsIds() { + Subject subject = getSubject(); + return (Set) subject + .getPrincipals(osGroupIdPrincipalClass); + } + + /** @return the subject always non null */ + protected Subject getSubject() { + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject == null) + throw new ArgeoException("No subject in JAAS context"); + return subject; + } + + public Object getCredentials() { + return ""; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java new file mode 100644 index 000000000..e5b8ae79c --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.security.Authentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; + +/** Static utilities */ +public class SecurityUtils { + + private SecurityUtils() { + } + + /** Whether the current thread has the admin role */ + public static boolean hasCurrentThreadAuthority(String authority) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + for (GrantedAuthority ga : authentication.getAuthorities()) + if (ga.getAuthority().equals(authority)) + return true; + } + } + return false; + } + + /** + * @return the authenticated username or null if not authenticated / + * anonymous + */ + public static String getCurrentThreadUsername() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + if (authentication instanceof AnonymousAuthenticationToken) { + return null; + } + return authentication.getName(); + } + } + return null; + } + + /** + * Returns the display name of the user details (by calling toString() on + * it) + */ + public static String getUserDetailsDisplayName() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + Authentication authentication = securityContext.getAuthentication(); + if (authentication != null) { + if (authentication instanceof AnonymousAuthenticationToken) { + return null; + } + Object details = authentication.getDetails(); + if (details != null) + return details.toString(); + return authentication.getName(); + } + } + return null; + } + + /** + * Converts an array of Spring Security {@link GrantedAuthority} to a + * read-only list of strings, for portability and integration + */ + public static List authoritiesToStringList( + GrantedAuthority[] authorities) { + List lst = new ArrayList(); + for (GrantedAuthority ga : authorities) + lst.add(ga.getAuthority()); + return Collections.unmodifiableList(lst); + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/SystemAuthentication.java b/org.argeo.security.core/src/org/argeo/security/SystemAuthentication.java new file mode 100644 index 000000000..2722c1f7d --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/SystemAuthentication.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +/** + * Marks a system authentication, that is which did not require a login process. + */ +public interface SystemAuthentication { + +} diff --git a/org.argeo.security.core/src/org/argeo/security/SystemExecutionService.java b/org.argeo.security.core/src/org/argeo/security/SystemExecutionService.java new file mode 100644 index 000000000..075a6c3d8 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/SystemExecutionService.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; + +/** + * Allows to execute code authenticated as a system user (that is not a real + * person). The {@link Executor} interface is not used directly in order to + * allow future extension of this interface and to simplify its publication + * (e.g. as an OSGi service) and interception. + */ +public interface SystemExecutionService extends Executor { + /** + * Executes this {@link Runnable} within a system authenticated context. + * Implementations should make sure that this method is properly secured via + * Java permissions since it could access everything without credentials. + */ + public void execute(Runnable runnable); + + /** + * Executes this {@link Callable} within a system authenticated context. + * Implementations should make sure that this method is properly secured via + * Java permissions since it could access everything without credentials. + */ + public Future submit(Callable task); +} diff --git a/org.argeo.security.core/src/org/argeo/security/UserAdminService.java b/org.argeo.security.core/src/org/argeo/security/UserAdminService.java new file mode 100644 index 000000000..0a84cf66f --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/UserAdminService.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security; + +import java.util.Set; + +import org.springframework.security.userdetails.UserDetailsManager; + +/** Enrich {@link UserDetailsManager} in order to provide roles semantics. */ +public interface UserAdminService extends UserDetailsManager { + /** + * Usernames must match this regexp pattern ({@value #USERNAME_PATTERN}). + * Thanks to this tip (modified to add upper-case, add '@') + */ + //public final static String USERNAME_PATTERN = "^[a-zA-Z0-9_-@]{3,64}$"; + + /** + * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. + * Thanks to this tip. + */ + public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; + + /* + * USERS + */ + /** List all users. */ + public Set listUsers(); + + /** List users having this role (except the super user). */ + public Set listUsersInRole(String role); + + /** Synchronize with the underlying DAO. */ + public void synchronize(); + + /* + * ROLES + */ + public void newRole(String role); + + public Set listEditableRoles(); + + public void deleteRole(String role); +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java b/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java new file mode 100644 index 000000000..b84f3de00 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/AbstractSystemExecution.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.security.SystemAuthentication; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationManager; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; + +/** Provides base method for executing code with system authorization. */ +public abstract class AbstractSystemExecution { + static { + // Forces Spring Security to use inheritable strategy + // FIXME find a better place for forcing spring security mode + // doesn't work for the time being +// if (System.getProperty(SecurityContextHolder.SYSTEM_PROPERTY) == null) +// SecurityContextHolder +// .setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); + } + + private final static Log log = LogFactory + .getLog(AbstractSystemExecution.class); + private AuthenticationManager authenticationManager; + private String systemAuthenticationKey; + + /** Whether the current thread was authenticated by this component. */ + private ThreadLocal authenticatedBySelf = new ThreadLocal() { + protected Boolean initialValue() { + return false; + } + }; + + /** + * Authenticate the calling thread to the underlying + * {@link AuthenticationManager} + */ + protected void authenticateAsSystem() { + if (authenticatedBySelf.get()) + return; + SecurityContext securityContext = SecurityContextHolder.getContext(); + Authentication currentAuth = securityContext.getAuthentication(); + if (currentAuth != null) { + if (!(currentAuth instanceof SystemAuthentication)) + throw new ArgeoException( + "System execution on an already authenticated thread: " + + currentAuth + ", THREAD=" + + Thread.currentThread().getId()); + return; + } + // Subject subject = Subject.getSubject(AccessController.getContext()); + // if (subject != null + // && !subject.getPrincipals(Authentication.class).isEmpty()) + // throw new ArgeoException( + // "There is already an authenticated subject: " + subject); + + String key = systemAuthenticationKey != null ? systemAuthenticationKey + : System.getProperty( + InternalAuthentication.SYSTEM_KEY_PROPERTY, + InternalAuthentication.SYSTEM_KEY_DEFAULT); + if (key == null) + throw new ArgeoException("No system key defined"); + Authentication auth = authenticationManager + .authenticate(new InternalAuthentication(key)); + securityContext.setAuthentication(auth); + authenticatedBySelf.set(true); + if (log.isTraceEnabled()) + log.trace("System authenticated"); + } + + // /** Removes the authentication from the calling thread. */ + // protected void deauthenticateAsSystem() { + // // remove the authentication + // // SecurityContext securityContext = SecurityContextHolder.getContext(); + // // securityContext.setAuthentication(null); + // // authenticatedBySelf.set(false); + // if (log.isTraceEnabled()) { + // log.trace("System deauthenticated"); + // // Thread.dumpStack(); + // } + // } + + /** + * Whether the current thread was authenticated by this component or a + * parent thread. + */ + protected Boolean isAuthenticatedBySelf() { + return authenticatedBySelf.get(); + } + + public void setAuthenticationManager( + AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setSystemAuthenticationKey(String systemAuthenticationKey) { + this.systemAuthenticationKey = systemAuthenticationKey; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/AsyncSystemTaskExecutor.java b/org.argeo.security.core/src/org/argeo/security/core/AsyncSystemTaskExecutor.java new file mode 100644 index 000000000..0e400c82d --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/AsyncSystemTaskExecutor.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import org.argeo.security.SystemExecutionService; +import org.springframework.core.task.SimpleAsyncTaskExecutor; + +/** + * Asynchronous Spring TaskExecutor (for use in JMS for example) wrapping a + * {@link SystemExecutionService}. + */ +public class AsyncSystemTaskExecutor extends SimpleAsyncTaskExecutor { + private static final long serialVersionUID = -8035527542087963068L; + + private SystemExecutionService systemExecutionService; + + public AsyncSystemTaskExecutor() { + super(); + } + + public AsyncSystemTaskExecutor(String threadNamePrefix) { + super(threadNamePrefix); + } + + @Override + public Thread createThread(final Runnable runnable) { + Runnable systemExecutionRunnable = new Runnable() { + + public void run() { + systemExecutionService.execute(runnable); + + } + }; + return super.createThread(systemExecutionRunnable); + } + + public void setSystemExecutionService( + SystemExecutionService systemExecutionService) { + this.systemExecutionService = systemExecutionService; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java b/org.argeo.security.core/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java new file mode 100644 index 000000000..97dd6cae0 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/AuthenticatedApplicationContextInitialization.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; + +/** + * Executes with a system authentication the instantiation and initialization + * methods of the application context where it has been defined. + */ +public class AuthenticatedApplicationContextInitialization extends + AbstractSystemExecution implements InstantiationAwareBeanPostProcessor, + ApplicationListener { + // private Log log = LogFactory + // .getLog(AuthenticatedApplicationContextInitialization.class); + /** If non empty, restricts to these beans */ + private List beanNames = new ArrayList(); + + @SuppressWarnings("rawtypes") + public Object postProcessBeforeInstantiation(Class beanClass, + String beanName) throws BeansException { + // we authenticate when any bean is instantiated + // we will deauthenticate only when the application context has been + // refreshed in order to be able to deal with factory beans has well + if (!isAuthenticatedBySelf()) { + if (beanNames.size() == 0) + authenticateAsSystem(); + else if (beanNames.contains(beanName)) + authenticateAsSystem(); + } + return null; + } + + public boolean postProcessAfterInstantiation(Object bean, String beanName) + throws BeansException { + return true; + } + + public PropertyValues postProcessPropertyValues(PropertyValues pvs, + PropertyDescriptor[] pds, Object bean, String beanName) + throws BeansException { + return pvs; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + // authenticateAsSystem(); + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + // NOTE: in case there was an exception in on the initialization method + // we expect the underlying thread to die and thus the system + // authentication to be lost. We have currently no way to catch the + // exception and perform the deauthentication by ourselves. + // deauthenticateAsSystem(); + return bean; + } + + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ContextRefreshedEvent) { + // make sure that we have deauthenticated after the application + // context was initialized/refreshed + // deauthenticateAsSystem(); + } + } + + public void setBeanNames(List beanNames) { + this.beanNames = beanNames; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/AuthenticationProvidersRegister.java b/org.argeo.security.core/src/org/argeo/security/core/AuthenticationProvidersRegister.java new file mode 100644 index 000000000..317815e8b --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/AuthenticationProvidersRegister.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * Maintains a list of authentication providers injected in to a provider + * manager, in order to avoid issues with OSGi services and use packages. + */ +public class AuthenticationProvidersRegister implements InitializingBean { + private Log log = LogFactory.getLog(AuthenticationProvidersRegister.class); + + private List providers = new ArrayList(); + private List defaultProviders = new ArrayList(); + + public void register(Object authenticationProvider, + Map parameters) { + providers.add(authenticationProvider); + if (log.isTraceEnabled()) + log.trace("Registered authentication provider " + parameters); + } + + public void unregister(Object authenticationProvider, + Map parameters) { + providers.remove(authenticationProvider); + if (log.isTraceEnabled()) + log.trace("Unregistered authentication provider " + parameters); + } + + public List getProviders() { + return providers; + } + + public void setDefaultProviders( + List defaultProviders) { + this.defaultProviders = defaultProviders; + } + + public void afterPropertiesSet() throws Exception { + providers.addAll(defaultProviders); + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/ConsoleCallbackHandler.java b/org.argeo.security.core/src/org/argeo/security/core/ConsoleCallbackHandler.java new file mode 100644 index 000000000..faa81b004 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/ConsoleCallbackHandler.java @@ -0,0 +1,70 @@ +package org.argeo.security.core; + +import java.io.Console; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Locale; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.argeo.ArgeoException; +import org.argeo.util.LocaleCallback; + +/** Callback handler to be used with a command line UI. */ +public class ConsoleCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + Console console = System.console(); + if (console == null) + throw new ArgeoException("No console available"); + + PrintWriter writer = console.writer(); + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof TextOutputCallback) { + TextOutputCallback callback = (TextOutputCallback) callbacks[i]; + writer.write(callback.getMessage()); + } else if (callbacks[i] instanceof NameCallback) { + NameCallback callback = (NameCallback) callbacks[i]; + writer.write(callback.getPrompt()); + if (callback.getDefaultName() != null) + writer.write(" (" + callback.getDefaultName() + ")"); + writer.write(" : "); + String answer = console.readLine(); + if (callback.getDefaultName() != null + && answer.trim().equals("")) + callback.setName(callback.getDefaultName()); + else + callback.setName(answer); + } else if (callbacks[i] instanceof PasswordCallback) { + PasswordCallback callback = (PasswordCallback) callbacks[i]; + writer.write(callback.getPrompt()); + char[] answer = console.readPassword(); + callback.setPassword(answer); + Arrays.fill(answer, ' '); + } else if (callbacks[i] instanceof LocaleCallback) { + LocaleCallback callback = (LocaleCallback) callbacks[i]; + writer.write(callback.getPrompt()); + writer.write("\n"); + for (int j = 0; j < callback.getAvailableLocales().size(); j++) { + Locale locale = callback.getAvailableLocales().get(j); + writer.print(j + " : " + locale.getDisplayName() + "\n"); + } + writer.write("(" + callback.getDefaultIndex() + ") : "); + String answer = console.readLine(); + if (answer.trim().equals("")) + callback.setSelectedIndex(callback.getDefaultIndex()); + else + callback.setSelectedIndex(new Integer(answer.trim())); + } + } + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/InternalAuthentication.java b/org.argeo.security.core/src/org/argeo/security/core/InternalAuthentication.java new file mode 100644 index 000000000..267ddd312 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/InternalAuthentication.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import org.argeo.security.SystemAuthentication; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.adapters.PrincipalSpringSecurityUserToken; + +/** A token base on a system key used to request a system authentication. */ +public class InternalAuthentication extends PrincipalSpringSecurityUserToken + implements SystemAuthentication { + private static final long serialVersionUID = -6783376375615949315L; + /** 'admin' for consistency with JCR */ + public final static String DEFAULT_SYSTEM_USERNAME = "admin"; + public final static String DEFAULT_SYSTEM_ROLE = "ROLE_SYSTEM"; + public final static String SYSTEM_KEY_PROPERTY = "argeo.security.systemKey"; + public final static String SYSTEM_KEY_DEFAULT = "argeo"; + + public InternalAuthentication(String key, String systemUsername, + String systemRole) { + super( + key, + systemUsername, + key, + new GrantedAuthority[] { new GrantedAuthorityImpl(systemRole) }, + systemUsername); + } + + public InternalAuthentication(String key) { + this(key, DEFAULT_SYSTEM_USERNAME, DEFAULT_SYSTEM_ROLE); + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/KeyBasedSystemExecutionService.java b/org.argeo.security.core/src/org/argeo/security/core/KeyBasedSystemExecutionService.java new file mode 100644 index 000000000..6c85df1d1 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/KeyBasedSystemExecutionService.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import org.argeo.ArgeoException; +import org.argeo.security.SystemExecutionService; + +/** + * Implementation of a {@link SystemExecutionService} using a key-based + * {@link InternalAuthentication} + */ +public class KeyBasedSystemExecutionService extends AbstractSystemExecution + implements SystemExecutionService { + public void execute(Runnable runnable) { + try { + wrapWithSystemAuthentication(Executors.callable(runnable)).call(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ArgeoException( + "Exception when running system authenticated task", e); + } + } + + public Future submit(Callable task) { + FutureTask future = new FutureTask( + wrapWithSystemAuthentication(task)); + future.run(); + return future; + } + + protected Callable wrapWithSystemAuthentication( + final Callable runnable) { + return new Callable() { + + public T call() throws Exception { + authenticateAsSystem(); + try { + return runnable.call(); + } finally { +// deauthenticateAsSystem(); + } + } + }; + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/MatchingAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/core/MatchingAuthenticationProvider.java new file mode 100644 index 000000000..0471151a2 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/MatchingAuthenticationProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.springframework.core.io.Resource; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.userdetails.User; +import org.springframework.security.userdetails.UserDetails; + +/** @deprecated */ +@Deprecated +public class MatchingAuthenticationProvider extends + AbstractUserDetailsAuthenticationProvider { + + private Resource mapping; + private Properties properties; + + private List defaultRoles = new ArrayList(); + + @Override + protected void doAfterPropertiesSet() throws Exception { + properties = new Properties(); + InputStream propIn = mapping.getInputStream(); + try { + properties.load(propIn); + } finally { + propIn.close(); + } + } + + @Override + protected void additionalAuthenticationChecks(UserDetails userDetails, + UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + if (!userDetails.getPassword().equals(authentication.getCredentials())) + throw new BadCredentialsException( + "Invalid credentails provided by " + + authentication.getName()); + } + + @Override + protected UserDetails retrieveUser(String username, + UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + String value = properties.getProperty(username); + if (value == null) + throw new BadCredentialsException("User " + username + + " is not registered"); + List grantedAuthorities = new ArrayList(); + for (String role : defaultRoles) + grantedAuthorities.add(new GrantedAuthorityImpl(role)); + return new User( + username, + value, + true, + true, + true, + true, + grantedAuthorities + .toArray(new GrantedAuthority[grantedAuthorities.size()])); + } + + public void setMapping(Resource mapping) { + this.mapping = mapping; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/OsAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/core/OsAuthenticationProvider.java new file mode 100644 index 000000000..0e29ecd59 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/OsAuthenticationProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.core; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.security.OsAuthenticationToken; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.AuthenticationProvider; + +/** + * Validates an OS authentication. The id is that it will always be + * authenticated since we are always runnign within an OS, but the fact that the + * {@link Authentication} works properly depends on the proper OS login module + * having been called as well. TODO make it more configurable (base roles, is + * admin) + */ +public class OsAuthenticationProvider implements AuthenticationProvider { + final static String osUserRole = "ROLE_OS_USER"; + final static String userRole = "ROLE_USER"; + final static String adminRole = "ROLE_ADMIN"; + + final static Boolean isAdmin = true; + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + return new OsAuthenticationToken(getBaseAuthorities()); + } + + public static GrantedAuthority[] getBaseAuthorities() { + List auths = new ArrayList(); + auths.add(new GrantedAuthorityImpl(osUserRole)); + auths.add(new GrantedAuthorityImpl(userRole)); + if (isAdmin) + auths.add(new GrantedAuthorityImpl(adminRole)); + return auths.toArray(new GrantedAuthority[auths.size()]); + } + + @SuppressWarnings("rawtypes") + public boolean supports(Class authentication) { + return OsAuthenticationToken.class.isAssignableFrom(authentication); + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/OsgiModuleLabel.java b/org.argeo.security.core/src/org/argeo/security/core/OsgiModuleLabel.java new file mode 100644 index 000000000..45c9e16b0 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/OsgiModuleLabel.java @@ -0,0 +1,41 @@ +package org.argeo.security.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; + +/** + * Logs the name and version of an OSGi bundle based on its + * {@link BundleContext}. + */ +public class OsgiModuleLabel { + private final static Log log = LogFactory.getLog(OsgiModuleLabel.class); + + private Bundle bundle; + + public OsgiModuleLabel() { + } + + /** Sets without logging. */ + public OsgiModuleLabel(Bundle bundle) { + this.bundle = bundle; + } + + /** + * Retrieved bundle from a bundle context and logs it. Typically to be set + * as a Spring bean. + */ + public void setBundleContext(BundleContext bundleContext) { + this.bundle = bundleContext.getBundle(); + log.info(msg()); + } + + public String msg() { + String name = bundle.getHeaders().get(Constants.BUNDLE_NAME).toString(); + String symbolicName = bundle.getSymbolicName(); + String version = bundle.getVersion().toString(); + return name + " v" + version + " (" + symbolicName + ")"; + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/core/SimpleRoleRegistration.java b/org.argeo.security.core/src/org/argeo/security/core/SimpleRoleRegistration.java new file mode 100644 index 000000000..aa8a5f06e --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/core/SimpleRoleRegistration.java @@ -0,0 +1,57 @@ +package org.argeo.security.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.security.UserAdminService; + +/** + * Register one or many roles via a user admin service. Does nothing if the role + * is already registered. + */ +public class SimpleRoleRegistration implements Runnable { + private final static Log log = LogFactory + .getLog(SimpleRoleRegistration.class); + + private String role; + private List roles = new ArrayList(); + private UserAdminService userAdminService; + + @Override + public void run() { + Set existingRoles = userAdminService.listEditableRoles(); + if (role != null && !existingRoles.contains(role)) + newRole(role); + for (String r : roles) { + if (!existingRoles.contains(r)) + newRole(r); + } + } + + protected void newRole(String r) { + userAdminService.newRole(r); + log.info("Added role " + r + " required by application."); + } + + public void register(UserAdminService userAdminService, Map properties) { + this.userAdminService = userAdminService; + run(); + } + + public void setRole(String role) { + this.role = role; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public void setUserAdminService(UserAdminService userAdminService) { + this.userAdminService = userAdminService; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java b/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java new file mode 100644 index 000000000..daa1ebd12 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.security.AccessController; +import java.security.MessageDigest; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +import java.util.Iterator; + +import javax.crypto.SecretKey; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.ArgeoException; +import org.argeo.StreamUtils; +import org.argeo.util.security.Keyring; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** username / password based keyring. TODO internationalize */ +public abstract class AbstractKeyring implements Keyring, CryptoKeyring { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING"; + + private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT; + private CallbackHandler defaultCallbackHandler; + + private String charset = "UTF-8"; + + /** + * Default provider is bouncy castle, in order to have consistent behaviour + * across implementations + */ + private String securityProviderName = "BC"; + + /** + * Whether the keyring has already been created in the past with a master + * password + */ + protected abstract Boolean isSetup(); + + /** + * Setup the keyring persistently, {@link #isSetup()} must return true + * afterwards + */ + protected abstract void setup(char[] password); + + /** Populates the key spec callback */ + protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback); + + protected abstract void encrypt(String path, InputStream unencrypted); + + protected abstract InputStream decrypt(String path); + + /** Triggers lazy initialization */ + protected SecretKey getSecretKey() { + Subject subject = Subject.getSubject(AccessController.getContext()); + // we assume only one secrete key is available + Iterator iterator = subject.getPrivateCredentials( + SecretKey.class).iterator(); + if (!iterator.hasNext()) {// not initialized + CallbackHandler callbackHandler = new KeyringCallbackHandler(); + try { + LoginContext loginContext = new LoginContext(loginContextName, + subject, callbackHandler); + loginContext.login(); + // FIXME will login even if password is wrong + iterator = subject.getPrivateCredentials(SecretKey.class) + .iterator(); + return iterator.next(); + } catch (LoginException e) { + throw new ArgeoException("Keyring login failed", e); + } + + } else { + SecretKey secretKey = iterator.next(); + if (iterator.hasNext()) + throw new ArgeoException( + "More than one secret key in private credentials"); + return secretKey; + } + } + + public InputStream getAsStream(String path) { + return decrypt(path); + } + + public void set(String path, InputStream in) { + encrypt(path, in); + } + + public char[] getAsChars(String path) { + InputStream in = getAsStream(path); + CharArrayWriter writer = null; + Reader reader = null; + try { + writer = new CharArrayWriter(); + reader = new InputStreamReader(in, charset); + StreamUtils.copy(reader, writer); + return writer.toCharArray(); + } catch (IOException e) { + throw new ArgeoException("Cannot decrypt to char array", e); + } finally { + StreamUtils.closeQuietly(reader); + StreamUtils.closeQuietly(in); + StreamUtils.closeQuietly(writer); + } + } + + public void set(String path, char[] arr) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = null; + Writer writer = null; + try { + writer = new OutputStreamWriter(out, charset); + writer.write(arr); + writer.flush(); + in = new ByteArrayInputStream(out.toByteArray()); + set(path, in); + } catch (IOException e) { + throw new ArgeoException("Cannot encrypt to char array", e); + } finally { + StreamUtils.closeQuietly(writer); + StreamUtils.closeQuietly(out); + StreamUtils.closeQuietly(in); + } + } + + protected Provider getSecurityProvider() { + return Security.getProvider(securityProviderName); + } + + public void setLoginContextName(String loginContextName) { + this.loginContextName = loginContextName; + } + + public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) { + this.defaultCallbackHandler = defaultCallbackHandler; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public void setSecurityProviderName(String securityProviderName) { + this.securityProviderName = securityProviderName; + } + + @Deprecated + protected static byte[] hash(char[] password, byte[] salt, + Integer iterationCount) { + ByteArrayOutputStream out = null; + OutputStreamWriter writer = null; + try { + out = new ByteArrayOutputStream(); + writer = new OutputStreamWriter(out, "UTF-8"); + writer.write(password); + MessageDigest pwDigest = MessageDigest.getInstance("SHA-256"); + pwDigest.reset(); + pwDigest.update(salt); + byte[] btPass = pwDigest.digest(out.toByteArray()); + for (int i = 0; i < iterationCount; i++) { + pwDigest.reset(); + btPass = pwDigest.digest(btPass); + } + return btPass; + } catch (Exception e) { + throw new ArgeoException("Cannot hash", e); + } finally { + StreamUtils.closeQuietly(out); + StreamUtils.closeQuietly(writer); + } + + } + + /** + * Convenience method using the underlying callback to ask for a password + * (typically used when the password is not saved in the keyring) + */ + protected char[] ask() { + PasswordCallback passwordCb = new PasswordCallback("Password", false); + Callback[] dialogCbs = new Callback[] { passwordCb }; + try { + defaultCallbackHandler.handle(dialogCbs); + char[] password = passwordCb.getPassword(); + return password; + } catch (Exception e) { + throw new ArgeoException("Cannot ask for a password", e); + } + + } + + class KeyringCallbackHandler implements CallbackHandler { + public void handle(Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + // checks + if (callbacks.length != 2) + throw new IllegalArgumentException( + "Keyring required 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); + if (!(callbacks[0] instanceof PasswordCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + if (!(callbacks[1] instanceof PBEKeySpecCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + + PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; + PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; + + if (isSetup()) { + Callback[] dialogCbs = new Callback[] { passwordCb }; + defaultCallbackHandler.handle(dialogCbs); + } else {// setup keyring + TextOutputCallback textCb1 = new TextOutputCallback( + TextOutputCallback.INFORMATION, + "Enter a master password which will protect your private data"); + TextOutputCallback textCb2 = new TextOutputCallback( + TextOutputCallback.INFORMATION, + "(for example your credentials to third-party services)"); + TextOutputCallback textCb3 = new TextOutputCallback( + TextOutputCallback.INFORMATION, + "Don't forget this password since the data cannot be read without it"); + PasswordCallback confirmPasswordCb = new PasswordCallback( + "Confirm password", false); + // first try + Callback[] dialogCbs = new Callback[] { textCb1, textCb2, + textCb3, passwordCb, confirmPasswordCb }; + defaultCallbackHandler.handle(dialogCbs); + + // if passwords different, retry (except if cancelled) + while (passwordCb.getPassword() != null + && !Arrays.equals(passwordCb.getPassword(), + confirmPasswordCb.getPassword())) { + TextOutputCallback textCb = new TextOutputCallback( + TextOutputCallback.ERROR, + "The passwords do not match"); + dialogCbs = new Callback[] { textCb, passwordCb, + confirmPasswordCb }; + defaultCallbackHandler.handle(dialogCbs); + } + + if (passwordCb.getPassword() != null) {// not cancelled + setup(passwordCb.getPassword()); + } + } + + if (passwordCb.getPassword() != null) + handleKeySpecCallback(pbeCb); + } + + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java b/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java new file mode 100644 index 000000000..d25eccd22 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import org.argeo.util.security.Keyring; + +/** + * Advanced keyring based on cryptography that can easily be centralized and + * coordinated with {@link KeyringLoginModule} (since they ar ein the same + * package) + */ +public interface CryptoKeyring extends Keyring { + +} diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java b/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java new file mode 100644 index 000000000..34b7d4015 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import java.security.AccessController; +import java.util.Map; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +/** Adds a secret key to the private credentials */ +public class KeyringLoginModule implements LoginModule { + private Subject subject; + private CallbackHandler callbackHandler; + private SecretKey secretKey; + + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + this.subject = subject; + if (subject == null) { + subject = Subject.getSubject(AccessController.getContext()); + } + this.callbackHandler = callbackHandler; + } + + public boolean login() throws LoginException { + Set pbes = subject.getPrivateCredentials(SecretKey.class); + if (pbes.size() > 0) + return true; + PasswordCallback pc = new PasswordCallback("Master password", false); + PBEKeySpecCallback pbeCb = new PBEKeySpecCallback(); + Callback[] callbacks = { pc, pbeCb }; + try { + callbackHandler.handle(callbacks); + char[] password = pc.getPassword(); + + SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb + .getSecretKeyFactory()); + PBEKeySpec keySpec; + if (pbeCb.getKeyLength() != null) + keySpec = new PBEKeySpec(password, pbeCb.getSalt(), + pbeCb.getIterationCount(), pbeCb.getKeyLength()); + else + keySpec = new PBEKeySpec(password, pbeCb.getSalt(), + pbeCb.getIterationCount()); + + String secKeyEncryption = pbeCb.getSecretKeyEncryption(); + if (secKeyEncryption != null) { + SecretKey tmp = keyFac.generateSecret(keySpec); + secretKey = new SecretKeySpec(tmp.getEncoded(), + secKeyEncryption); + } else { + secretKey = keyFac.generateSecret(keySpec); + } + } catch (Exception e) { + LoginException le = new LoginException("Cannot login keyring"); + le.initCause(e); + throw le; + } + return true; + } + + public boolean commit() throws LoginException { + if (secretKey != null) + subject.getPrivateCredentials().add(secretKey); + return true; + } + + public boolean abort() throws LoginException { + return true; + } + + public boolean logout() throws LoginException { + Set pbes = subject + .getPrivateCredentials(PasswordBasedEncryption.class); + pbes.clear(); + return true; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java b/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java new file mode 100644 index 000000000..e96436664 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import javax.crypto.spec.PBEKeySpec; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.PasswordCallback; + +/** + * All information required to set up a {@link PBEKeySpec} bar the password + * itself (use a {@link PasswordCallback}) + */ +public class PBEKeySpecCallback implements Callback { + private String secretKeyFactory; + private byte[] salt; + private Integer iterationCount; + /** Can be null for some algorithms */ + private Integer keyLength; + /** Can be null, will trigger secret key encryption if not */ + private String secretKeyEncryption; + + private String encryptedPasswordHashCipher; + private byte[] encryptedPasswordHash; + + public void set(String secretKeyFactory, byte[] salt, + Integer iterationCount, Integer keyLength, + String secretKeyEncryption) { + this.secretKeyFactory = secretKeyFactory; + this.salt = salt; + this.iterationCount = iterationCount; + this.keyLength = keyLength; + this.secretKeyEncryption = secretKeyEncryption; +// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher; +// this.encryptedPasswordHash = encryptedPasswordHash; + } + + public String getSecretKeyFactory() { + return secretKeyFactory; + } + + public byte[] getSalt() { + return salt; + } + + public Integer getIterationCount() { + return iterationCount; + } + + public Integer getKeyLength() { + return keyLength; + } + + public String getSecretKeyEncryption() { + return secretKeyEncryption; + } + + public String getEncryptedPasswordHashCipher() { + return encryptedPasswordHashCipher; + } + + public byte[] getEncryptedPasswordHash() { + return encryptedPasswordHash; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java b/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java new file mode 100644 index 000000000..aec25ac17 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.crypto; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.Security; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.StreamUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** Simple password based encryption / decryption */ +public class PasswordBasedEncryption { + private final static Log log = LogFactory + .getLog(PasswordBasedEncryption.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public final static Integer DEFAULT_ITERATION_COUNT = 1024; + /** Stronger with 256, but causes problem with Oracle JVM */ + public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256; + public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128; + public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; + public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; + public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; + public final static String DEFAULT_CHARSET = "UTF-8"; + + private Integer iterationCount = DEFAULT_ITERATION_COUNT; + private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; + private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; + private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; + private String cipherName = DEFAULT_CIPHER_NAME; + + private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, + (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, + (byte) 0x03 }; + private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, + (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, + (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, + (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; + + private Key key; + private Cipher ecipher; + private Cipher dcipher; + + /** + * Default provider is bouncy castle, in order to have consistent behaviour + * across implementations + */ + private String securityProviderName = "BC"; + + /** + * This is up to the caller to clear the passed array. Neither copy of nor + * reference to the passed array is kept + */ + public PasswordBasedEncryption(char[] password) { + this(password, DEFAULT_SALT_8, DEFAULT_IV_16); + } + + /** + * This is up to the caller to clear the passed array. Neither copies of nor + * references to the passed arrays are kept + */ + public PasswordBasedEncryption(char[] password, byte[] passwordSalt, + byte[] initializationVector) { + try { + initKeyAndCiphers(password, passwordSalt, initializationVector); + } catch (InvalidKeyException e) { + Integer previousSecreteKeyLength = secreteKeyLength; + secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED; + log.warn("'" + e.getMessage() + "', will use " + secreteKeyLength + + " secrete key length instead of " + + previousSecreteKeyLength); + try { + initKeyAndCiphers(password, passwordSalt, initializationVector); + } catch (Exception e1) { + throw new ArgeoException( + "Cannot get secret key (with restricted length)", e1); + } + } catch (Exception e) { + throw new ArgeoException("Cannot get secret key", e); + } + } + + protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, + byte[] initializationVector) throws GeneralSecurityException { + byte[] salt = new byte[8]; + System.arraycopy(passwordSalt, 0, salt, 0, salt.length); + // for (int i = 0; i < password.length && i < salt.length; i++) + // salt[i] = (byte) password[i]; + byte[] iv = new byte[16]; + System.arraycopy(initializationVector, 0, iv, 0, iv.length); + + SecretKeyFactory keyFac = SecretKeyFactory + .getInstance(getSecretKeyFactoryName()); + PBEKeySpec keySpec = new PBEKeySpec(password, salt, + getIterationCount(), getKeyLength()); + String secKeyEncryption = getSecretKeyEncryption(); + if (secKeyEncryption != null) { + SecretKey tmp = keyFac.generateSecret(keySpec); + key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption()); + } else { + key = keyFac.generateSecret(keySpec); + } + ecipher = Cipher.getInstance(getCipherName(), securityProviderName); + ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); + dcipher = Cipher.getInstance(getCipherName()); + dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + } + + public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) + throws IOException { + try { + CipherOutputStream out = new CipherOutputStream(encryptedOut, + ecipher); + StreamUtils.copy(decryptedIn, out); + StreamUtils.closeQuietly(out); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ArgeoException("Cannot encrypt", e); + } finally { + StreamUtils.closeQuietly(decryptedIn); + } + } + + public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) + throws IOException { + try { + CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, + dcipher); + StreamUtils.copy(decryptedIn, decryptedOut); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ArgeoException("Cannot decrypt", e); + } finally { + StreamUtils.closeQuietly(encryptedIn); + } + } + + public byte[] encryptString(String str) { + ByteArrayOutputStream out = null; + ByteArrayInputStream in = null; + try { + out = new ByteArrayOutputStream(); + in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET)); + encrypt(in, out); + return out.toByteArray(); + } catch (Exception e) { + throw new ArgeoException("Cannot encrypt", e); + } finally { + StreamUtils.closeQuietly(out); + } + } + + /** Closes the input stream */ + public String decryptAsString(InputStream in) { + ByteArrayOutputStream out = null; + try { + out = new ByteArrayOutputStream(); + decrypt(in, out); + return new String(out.toByteArray(), DEFAULT_CHARSET); + } catch (Exception e) { + throw new ArgeoException("Cannot decrypt", e); + } finally { + StreamUtils.closeQuietly(out); + } + } + + protected Key getKey() { + return key; + } + + protected Cipher getEcipher() { + return ecipher; + } + + protected Cipher getDcipher() { + return dcipher; + } + + protected Integer getIterationCount() { + return iterationCount; + } + + protected Integer getKeyLength() { + return secreteKeyLength; + } + + protected String getSecretKeyFactoryName() { + return secreteKeyFactoryName; + } + + protected String getSecretKeyEncryption() { + return secreteKeyEncryption; + } + + protected String getCipherName() { + return cipherName; + } + + public void setIterationCount(Integer iterationCount) { + this.iterationCount = iterationCount; + } + + public void setSecreteKeyLength(Integer keyLength) { + this.secreteKeyLength = keyLength; + } + + public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { + this.secreteKeyFactoryName = secreteKeyFactoryName; + } + + public void setSecreteKeyEncryption(String secreteKeyEncryption) { + this.secreteKeyEncryption = secreteKeyEncryption; + } + + public void setCipherName(String cipherName) { + this.cipherName = cipherName; + } + + public void setSecurityProviderName(String securityProviderName) { + this.securityProviderName = securityProviderName; + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java b/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java new file mode 100644 index 000000000..1b9f24426 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.InputStream; +import java.io.Reader; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.io.IOUtils; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; +import org.argeo.security.crypto.AbstractKeyring; +import org.argeo.security.crypto.PBEKeySpecCallback; + +/** JCR based implementation of a keyring */ +public class JcrKeyring extends AbstractKeyring implements ArgeoNames { + /** + * Stronger with 256, but causes problem with Oracle JVM, force 128 in this + * case + */ + public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l; + public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; + public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; + public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; + + private Integer iterationCountFactor = 200; + private Long secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; + private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; + private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; + private String cipherName = DEFAULT_CIPHER_NAME; + + private Session session; + + /** + * When setup is called the session has not yet been saved and we don't want + * to save it since there maybe other data which would be inconsistent. So + * we keep a reference to this node which will then be used (an reset to + * null) when handling the PBE callback. We keep one per thread in case + * multiple users are accessing the same instance of a keyring. + */ + private ThreadLocal notYetSavedKeyring = new ThreadLocal() { + + @Override + protected Node initialValue() { + return null; + } + }; + + @Override + protected Boolean isSetup() { + try { + if (notYetSavedKeyring.get() != null) + return true; + + Node userHome = UserJcrUtils.getUserHome(session); + return userHome.hasNode(ARGEO_KEYRING); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot check whether keyring is setup", e); + } + } + + @Override + protected void setup(char[] password) { + Binary binary = null; + InputStream in = null; + try { + Node userHome = UserJcrUtils.getUserHome(session); + if (userHome.hasNode(ARGEO_KEYRING)) + throw new ArgeoException("Keyring already setup"); + Node keyring = userHome.addNode(ARGEO_KEYRING); + keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC); + + // deterministic salt and iteration count based on username + String username = session.getUserID(); + byte[] salt = new byte[8]; + byte[] usernameBytes = username.getBytes(); + for (int i = 0; i < salt.length; i++) { + if (i < usernameBytes.length) + salt[i] = usernameBytes[i]; + else + salt[i] = 0; + } + in = new ByteArrayInputStream(salt); + binary = session.getValueFactory().createBinary(in); + keyring.setProperty(ARGEO_SALT, binary); + + Integer iterationCount = username.length() * iterationCountFactor; + keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount); + + // default algo + // TODO check if algo and key length are available, use DES if not + keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secreteKeyFactoryName); + keyring.setProperty(ARGEO_KEY_LENGTH, secreteKeyLength); + keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, + secreteKeyEncryption); + keyring.setProperty(ARGEO_CIPHER, cipherName); + + // encrypted password hash + // IOUtils.closeQuietly(in); + // JcrUtils.closeQuietly(binary); + // byte[] btPass = hash(password, salt, iterationCount); + // in = new ByteArrayInputStream(btPass); + // binary = session.getValueFactory().createBinary(in); + // keyring.setProperty(ARGEO_PASSWORD, binary); + + notYetSavedKeyring.set(keyring); + } catch (Exception e) { + throw new ArgeoException("Cannot setup keyring", e); + } finally { + JcrUtils.closeQuietly(binary); + IOUtils.closeQuietly(in); + // JcrUtils.discardQuietly(session); + } + } + + @Override + protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) { + try { + Node userHome = UserJcrUtils.getUserHome(session); + Node keyring; + if (userHome.hasNode(ARGEO_KEYRING)) + keyring = userHome.getNode(ARGEO_KEYRING); + else if (notYetSavedKeyring.get() != null) + keyring = notYetSavedKeyring.get(); + else + throw new ArgeoException("Keyring not setup"); + + pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY) + .getString(), JcrUtils.getBinaryAsBytes(keyring + .getProperty(ARGEO_SALT)), + (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(), + (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(), + keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION) + .getString()); + + if (notYetSavedKeyring.get() != null) + notYetSavedKeyring.remove(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot handle key spec callback", e); + } + } + + /** The parent node must already exist at this path. */ + @Override + protected synchronized void encrypt(String path, InputStream unencrypted) { + // should be called first for lazy initialization + SecretKey secretKey = getSecretKey(); + + Binary binary = null; + InputStream in = null; + try { + Cipher cipher = createCipher(); + Node node; + if (!session.nodeExists(path)) { + String parentPath = JcrUtils.parentPath(path); + if (!session.nodeExists(parentPath)) + throw new ArgeoException("No parent node of " + path); + Node parentNode = session.getNode(parentPath); + node = parentNode.addNode(JcrUtils.nodeNameFromPath(path)); + } else { + node = session.getNode(path); + } + node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); + JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); + + in = new CipherInputStream(unencrypted, cipher); + binary = session.getValueFactory().createBinary(in); + node.setProperty(Property.JCR_DATA, binary); + session.save(); + } catch (Exception e) { + throw new ArgeoException("Cannot encrypt", e); + } finally { + IOUtils.closeQuietly(unencrypted); + IOUtils.closeQuietly(in); + JcrUtils.closeQuietly(binary); + } + } + + @Override + protected synchronized InputStream decrypt(String path) { + Binary binary = null; + InputStream encrypted = null; + Reader reader = null; + try { + if (!session.nodeExists(path)) { + char[] password = ask(); + reader = new CharArrayReader(password); + return new ByteArrayInputStream(IOUtils.toByteArray(reader)); + } else { + // should be called first for lazy initialisation + SecretKey secretKey = getSecretKey(); + + Cipher cipher = createCipher(); + + Node node = session.getNode(path); + if (node.hasProperty(ARGEO_IV)) { + byte[] iv = JcrUtils.getBinaryAsBytes(node + .getProperty(ARGEO_IV)); + cipher.init(Cipher.DECRYPT_MODE, secretKey, + new IvParameterSpec(iv)); + } else { + cipher.init(Cipher.DECRYPT_MODE, secretKey); + } + + binary = node.getProperty(Property.JCR_DATA).getBinary(); + encrypted = binary.getStream(); + return new CipherInputStream(encrypted, cipher); + } + } catch (Exception e) { + throw new ArgeoException("Cannot decrypt", e); + } finally { + IOUtils.closeQuietly(encrypted); + IOUtils.closeQuietly(reader); + JcrUtils.closeQuietly(binary); + } + } + + protected Cipher createCipher() { + try { + Node userHome = UserJcrUtils.getUserHome(session); + if (!userHome.hasNode(ARGEO_KEYRING)) + throw new ArgeoException("Keyring not setup"); + Node keyring = userHome.getNode(ARGEO_KEYRING); + Cipher cipher = Cipher.getInstance(keyring + .getProperty(ARGEO_CIPHER).getString(), + getSecurityProvider()); + return cipher; + } catch (Exception e) { + throw new ArgeoException("Cannot get cipher", e); + } + } + + public synchronized void changePassword(char[] oldPassword, + char[] newPassword) { + // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted + } + + public synchronized void setSession(Session session) { + this.session = session; + } + + public void setIterationCountFactor(Integer iterationCountFactor) { + this.iterationCountFactor = iterationCountFactor; + } + + public void setSecreteKeyLength(Long keyLength) { + this.secreteKeyLength = keyLength; + } + + public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { + this.secreteKeyFactoryName = secreteKeyFactoryName; + } + + public void setSecreteKeyEncryption(String secreteKeyEncryption) { + this.secreteKeyEncryption = secreteKeyEncryption; + } + + public void setCipherName(String cipherName) { + this.cipherName = cipherName; + } + +} \ No newline at end of file diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java b/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java new file mode 100644 index 000000000..e9ab89c2a --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/JcrSecurityModel.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Session; + +/** + * Manages data expected by the Argeo security model, such as user home and + * profile. + */ +public interface JcrSecurityModel { + /** + * To be called before user details are loaded. Make sure than any logged in + * user has a home directory with full access and a profile with information + * about him (read access) + * + * @return the user profile (whose parent is the user home), never null + */ + public Node sync(Session session, String username, List roles); +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/JcrUserDetails.java b/org.argeo.security.core/src/org/argeo/security/jcr/JcrUserDetails.java new file mode 100644 index 000000000..2f7b97b78 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/JcrUserDetails.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.UserJcrUtils; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.DisabledException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.LockedException; +import org.springframework.security.userdetails.User; + +/** User details based on a user profile node. */ +public class JcrUserDetails extends User implements ArgeoNames { + private static final long serialVersionUID = -8142764995842559646L; + private final String homePath; + private final String securityWorkspace; + + /** Human readable user name */ + private String displayName; + + protected JcrUserDetails(String securityWorkspace, String homePath, + String username, String password, boolean enabled, + boolean accountNonExpired, boolean credentialsNonExpired, + boolean accountNonLocked, GrantedAuthority[] authorities) + throws IllegalArgumentException { + super(username, password, enabled, accountNonExpired, + credentialsNonExpired, accountNonLocked, authorities); + this.homePath = homePath; + this.securityWorkspace = securityWorkspace; + } + + public JcrUserDetails(Node userProfile, String password, + GrantedAuthority[] authorities) throws RepositoryException { + super( + userProfile.getProperty(ARGEO_USER_ID).getString(), + password, + userProfile.getProperty(ARGEO_ENABLED).getBoolean(), + userProfile.getProperty(ARGEO_ACCOUNT_NON_EXPIRED).getBoolean(), + userProfile.getProperty(ARGEO_CREDENTIALS_NON_EXPIRED) + .getBoolean(), userProfile.getProperty( + ARGEO_ACCOUNT_NON_LOCKED).getBoolean(), authorities); + // human readable name + if (userProfile.hasProperty(Property.JCR_TITLE)) { + displayName = userProfile.getProperty(Property.JCR_TITLE) + .getString(); + if (displayName.trim().equals("")) + displayName = null; + } + if (displayName == null) + displayName = userProfile.getProperty(ARGEO_USER_ID).getString(); + // home is defined as the parent of the profile + homePath = userProfile.getParent().getPath(); + securityWorkspace = userProfile.getSession().getWorkspace().getName(); + } + + /** + * Convenience constructor + * + * @param session + * the security session + * @param username + * the username + * @param password + * the password, can be null + * @param authorities + * the granted authorities + */ + public JcrUserDetails(Session session, String username, String password, + GrantedAuthority[] authorities) throws RepositoryException { + this(UserJcrUtils.getUserProfile(session, username), + password != null ? password : "", authorities); + } + + /** + * Check the account status in JCR, throwing the exceptions expected by + * Spring security if needed. + */ + public static void checkAccountStatus(Node userProfile) { + try { + if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) + throw new DisabledException(userProfile.getPath() + + " is disabled"); + if (!userProfile.getProperty(ARGEO_ACCOUNT_NON_LOCKED).getBoolean()) + throw new LockedException(userProfile.getPath() + " is locked"); + } catch (RepositoryException e) { + throw new BadCredentialsException("Cannot check account status", e); + } + } + + /** Clone immutable with new roles */ + public JcrUserDetails cloneWithNewRoles(List roles) { + List authorities = new ArrayList(); + for (String role : roles) { + authorities.add(new GrantedAuthorityImpl(role)); + } + return new JcrUserDetails(securityWorkspace, homePath, getUsername(), + getPassword(), isEnabled(), isAccountNonExpired(), + isAccountNonExpired(), isAccountNonLocked(), + authorities.toArray(new GrantedAuthority[authorities.size()])); + } + + /** Clone immutable with new password */ + public JcrUserDetails cloneWithNewPassword(String password) { + return new JcrUserDetails(securityWorkspace, homePath, getUsername(), + password, isEnabled(), isAccountNonExpired(), + isAccountNonExpired(), isAccountNonLocked(), getAuthorities()); + } + + public String getHomePath() { + return homePath; + } + + /** Not yet API */ + public String getSecurityWorkspace() { + return securityWorkspace; + } + + /** The human readable name of this user */ + public String getDisplayName() { + return displayName; + } + + @Override + public String toString() { + return getDisplayName(); + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java new file mode 100644 index 000000000..aa95e322d --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrAuthenticationProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; +import org.argeo.security.OsAuthenticationToken; +import org.argeo.security.SecurityUtils; +import org.argeo.security.core.OsAuthenticationProvider; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.security.userdetails.UserDetails; + +/** Relies on OS to authenticate and additionally setup JCR */ +public class OsJcrAuthenticationProvider extends OsAuthenticationProvider { + private Repository repository; + private Session nodeSession; + + private UserDetails userDetails; + private JcrSecurityModel jcrSecurityModel = new SimpleJcrSecurityModel(); + + private final static String JVM_OSUSER = System.getProperty("user.name"); + + public void init() { + try { + nodeSession = repository.login(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot initialize", e); + } + } + + public void destroy() { + JcrUtils.logoutQuietly(nodeSession); + } + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + if (authentication instanceof UsernamePasswordAuthenticationToken) { + // deal with remote access to internal server + // FIXME very primitive and unsecure at this sSession adminSession + // =tage + // consider using the keyring for username / password authentication + // or certificate + UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; + if (!upat.getPrincipal().toString().equals(JVM_OSUSER)) + throw new BadCredentialsException("Wrong credentials"); + UsernamePasswordAuthenticationToken authen = new UsernamePasswordAuthenticationToken( + authentication.getPrincipal(), + authentication.getCredentials(), getBaseAuthorities()); + authen.setDetails(userDetails); + return authen; + } else if (authentication instanceof OsAuthenticationToken) { + OsAuthenticationToken authen = (OsAuthenticationToken) super + .authenticate(authentication); + try { + // WARNING: at this stage we assume that the java properties + // will have the same value + GrantedAuthority[] authorities = getBaseAuthorities(); + String username = JVM_OSUSER; + Node userProfile = jcrSecurityModel.sync(nodeSession, username, + SecurityUtils.authoritiesToStringList(authorities)); + JcrUserDetails.checkAccountStatus(userProfile); + + userDetails = new JcrUserDetails(userProfile, authen + .getCredentials().toString(), authorities); + authen.setDetails(userDetails); + return authen; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(nodeSession); + throw new ArgeoException( + "Unexpected exception when synchronizing OS and JCR security ", + e); + } + } else { + throw new ArgeoException("Unsupported authentication " + + authentication.getClass()); + } + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setJcrSecurityModel(JcrSecurityModel jcrSecurityModel) { + this.jcrSecurityModel = jcrSecurityModel; + } + + @SuppressWarnings("rawtypes") + public boolean supports(Class authentication) { + return OsAuthenticationToken.class.isAssignableFrom(authentication) + || UsernamePasswordAuthenticationToken.class + .isAssignableFrom(authentication); + } +} \ No newline at end of file diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java new file mode 100644 index 000000000..c25bdb865 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/OsJcrUserAdminService.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; +import org.argeo.security.UserAdminService; +import org.springframework.dao.DataAccessException; +import org.springframework.security.userdetails.User; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UsernameNotFoundException; + +/** + * Dummy user service to be used when running as a single OS user (typically + * desktop). TODO integrate with JCR user / groups + */ +public class OsJcrUserAdminService implements UserAdminService { + private Repository repository; + + /** In memory roles provided by applications. */ + private List roles = new ArrayList(); + + // private Session adminSession; + + public void init() { + // try { + // adminSession = repository.login(); + // } catch (RepositoryException e) { + // throw new ArgeoException("Cannot initialize", e); + // } + } + + public void destroy() { + // JcrUtils.logoutQuietly(adminSession); + } + + /** Unsupported */ + public void createUser(UserDetails user) { + throw new UnsupportedOperationException(); + } + + /** Does nothing */ + public void updateUser(UserDetails user) { + + } + + /** Unsupported */ + public void deleteUser(String username) { + throw new UnsupportedOperationException(); + } + + /** Unsupported */ + public void changePassword(String oldPassword, String newPassword) { + throw new UnsupportedOperationException(); + } + + public boolean userExists(String username) { + if (getSPropertyUsername().equals(username)) + return true; + else + return false; + } + + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException, DataAccessException { + if (getSPropertyUsername().equals(username)) { + UserDetails userDetails; + if (repository != null) { + Session adminSession = null; + try { + adminSession = repository.login(); + Node userProfile = UserJcrUtils.getUserProfile( + adminSession, username); + userDetails = new JcrUserDetails(userProfile, "", + OsJcrAuthenticationProvider.getBaseAuthorities()); + } catch (RepositoryException e) { + throw new ArgeoException( + "Cannot retrieve user profile for " + username, e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } else { + userDetails = new User(username, "", true, true, true, true, + OsJcrAuthenticationProvider.getBaseAuthorities()); + } + return userDetails; + } else { + throw new UnsupportedOperationException(); + } + } + + protected final String getSPropertyUsername() { + return System.getProperty("user.name"); + } + + public Set listUsers() { + Set set = new HashSet(); + set.add(getSPropertyUsername()); + return set; + } + + public Set listUsersInRole(String role) { + Set set = new HashSet(); + set.add(getSPropertyUsername()); + return set; + } + + /** Does nothing */ + public void synchronize() { + } + + /** Unsupported */ + public void newRole(String role) { + roles.add(role); + } + + public Set listEditableRoles() { + return new HashSet(roles); + } + + /** Unsupported */ + public void deleteRole(String role) { + roles.remove(role); + } + + public void setRepository(Repository repository) { + this.repository = repository; + } +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java b/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java new file mode 100644 index 000000000..87208b2a3 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; +import javax.jcr.Value; + +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.UserJcrUtils; +import org.argeo.security.NodeAuthenticationToken; +import org.osgi.framework.BundleContext; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.AuthenticationProvider; + +/** Connects to a JCR repository and delegates authentication to it. */ +public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, + ArgeoNames { + private RepositoryFactory repositoryFactory; + private BundleContext bundleContext; + + public final static String ROLE_REMOTE = "ROLE_REMOTE"; + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication; + String url = siteAuth.getUrl(); + if (url == null)// TODO? login on own node + throw new ArgeoException("No url set in " + siteAuth); + Session session; + + Node userProfile; + try { + SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), + siteAuth.getCredentials().toString().toCharArray()); + // get repository + Repository repository = new RemoteJcrRepositoryWrapper( + repositoryFactory, url, sp); + if (bundleContext != null) { + Dictionary serviceProperties = new Hashtable(); + serviceProperties.put(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, + ArgeoJcrConstants.ALIAS_NODE); + serviceProperties + .put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); + bundleContext.registerService(Repository.class.getName(), + repository, serviceProperties); + } + // Repository repository = ArgeoJcrUtils.getRepositoryByUri( + // repositoryFactory, url); + // if (repository == null) + // throw new ArgeoException("Cannot connect to " + url); + + session = repository.login(sp, null); + + userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID()); + JcrUserDetails.checkAccountStatus(userProfile); + + // Node userHome = UserJcrUtils.getUserHome(session); + // if (userHome == null || + // !userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + // throw new ArgeoException("No profile for user " + // + siteAuth.getName() + " in security workspace " + // + siteAuth.getSecurityWorkspace() + " of " + // + siteAuth.getUrl()); + // userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); + } catch (RepositoryException e) { + throw new BadCredentialsException( + "Cannot authenticate " + siteAuth, e); + } + + try { + // Node userHome = UserJcrUtils.getUserHome(session); + // retrieve remote roles + List authoritiesList = new ArrayList(); + if (userProfile != null + && userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] roles = userProfile.getProperty( + ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + for (int i = 0; i < roles.length; i++) + authoritiesList.add(new GrantedAuthorityImpl(roles[i] + .getString())); + } + authoritiesList.add(new GrantedAuthorityImpl(ROLE_REMOTE)); + + // create authenticated objects + GrantedAuthority[] authorities = authoritiesList + .toArray(new GrantedAuthority[authoritiesList.size()]); + JcrUserDetails userDetails = new JcrUserDetails(userProfile, + siteAuth.getCredentials().toString(), authorities); + NodeAuthenticationToken authenticated = new NodeAuthenticationToken( + siteAuth, authorities); + authenticated.setDetails(userDetails); + return authenticated; + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when authenticating to " + url, e); + } + } + + @SuppressWarnings("rawtypes") + public boolean supports(Class authentication) { + return NodeAuthenticationToken.class.isAssignableFrom(authentication); + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java b/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java new file mode 100644 index 000000000..f0ad3a3a9 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import javax.jcr.Credentials; +import javax.jcr.LoginException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrUtils; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.security.NodeAuthenticationToken; +import org.argeo.security.SystemAuthentication; +import org.springframework.security.Authentication; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +/** + * Wrapper around a remote Jackrabbit repository which allows to simplify + * configuration and intercept some actions. It exposes itself as a + * {@link Repository}. + */ +public class RemoteJcrRepositoryWrapper extends JcrRepositoryWrapper { + private final static Log log = LogFactory + .getLog(RemoteJcrRepositoryWrapper.class); + + private String uri = null; + + private RepositoryFactory repositoryFactory; + + // remote + private Credentials remoteSystemCredentials = null; + + /** + * Empty constructor, {@link #init()} should be called after properties have + * been set + */ + public RemoteJcrRepositoryWrapper() { + } + + /** + * Embedded constructor, calling the {@link #init()} method. + * + * @param alias + * if not null the repository will be published under this alias + */ + public RemoteJcrRepositoryWrapper(RepositoryFactory repositoryFactory, + String uri, Credentials remoteSystemCredentials) { + this.repositoryFactory = repositoryFactory; + this.uri = uri; + this.remoteSystemCredentials = remoteSystemCredentials; + init(); + } + + public void init() { + Repository repository = createJackrabbitRepository(); + setRepository(repository); + } + + /** Actually creates the new repository. */ + protected Repository createJackrabbitRepository() { + long begin = System.currentTimeMillis(); + try { + if (uri == null || uri.trim().equals("")) + throw new ArgeoException("Remote URI not set"); + + Repository repository = ArgeoJcrUtils.getRepositoryByUri( + repositoryFactory, uri); + if (repository == null) + throw new ArgeoException("Remote JCR repository " + uri + + " not found"); + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + log.info("Created remote JCR repository in " + duration + + " s from URI " + uri); + // we assume that the data model of the remote repository has + // been properly initialized + return repository; + } catch (Exception e) { + throw new ArgeoException("Cannot create remote JCR repository " + + uri, e); + } + } + + /** Shutdown the repository */ + public void destroy() throws Exception { + super.destroy(); + } + + /** Central login method */ + public Session login(Credentials credentials, String workspaceName) + throws LoginException, NoSuchWorkspaceException, + RepositoryException { + + // retrieve credentials for remote + if (credentials == null) { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + if (authentication != null) { + if (authentication instanceof UsernamePasswordAuthenticationToken) { + UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; + credentials = new SimpleCredentials(upat.getName(), upat + .getCredentials().toString().toCharArray()); + } else if ((authentication instanceof SystemAuthentication) + || (authentication instanceof NodeAuthenticationToken)) { + credentials = remoteSystemCredentials; + } + } + } + + return super.login(credentials, workspaceName); + } + + public void setUri(String uri) { + this.uri = uri; + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) { + this.remoteSystemCredentials = remoteSystemCredentials; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java b/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java new file mode 100644 index 000000000..b39877420 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/SecureThreadBoundSession.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import javax.jcr.Session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.jcr.spring.ThreadBoundSession; +import org.springframework.security.Authentication; +import org.springframework.security.context.SecurityContextHolder; + +/** + * Thread bounded JCR session factory which checks authentication and is + * autoconfigured in Spring. + */ +public class SecureThreadBoundSession extends ThreadBoundSession { + private final static Log log = LogFactory + .getLog(SecureThreadBoundSession.class); + + @Override + protected Session preCall(Session session) { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + if (authentication != null) { + String userID = session.getUserID(); + String currentUserName = authentication.getName(); + if (currentUserName != null) { + if (!userID.equals(currentUserName)) { + log.warn("Current session has user ID " + userID + + " while logged is user is " + currentUserName + + "(authentication=" + authentication + ")" + + ". Re-login."); + // TODO throw an exception + return login(); + } + } + } + return super.preCall(session); + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java b/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java new file mode 100644 index 000000000..fc0158738 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/SimpleJcrSecurityModel.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.security.Privilege; +import javax.jcr.version.VersionManager; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; + +/** + * Manages data expected by the Argeo security model, such as user home and + * profile. + */ +public class SimpleJcrSecurityModel implements JcrSecurityModel { + private final static Log log = LogFactory + .getLog(SimpleJcrSecurityModel.class); + // ArgeoNames not implemented as interface in order to ease derivation by + // Jackrabbit bundles + + /** The home base path. */ + private String homeBasePath = "/home"; + + public synchronized Node sync(Session session, String username, + List roles) { + // TODO check user name validity (e.g. should not start by ROLE_) + + try { + Node userHome = UserJcrUtils.getUserHome(session, username); + if (userHome == null) { + String homePath = generateUserPath(homeBasePath, username); + userHome = JcrUtils.mkdirs(session, homePath); + // userHome = JcrUtils.mkfolders(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + session.save(); + + JcrUtils.clearAccessControList(session, homePath, username); + JcrUtils.addPrivilege(session, homePath, username, + Privilege.JCR_ALL); + } else { + // for backward compatibility with pre 1.0 security model + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { + userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove(); + userHome.getSession().save(); + } + } + + // Remote roles + if (roles != null) { + // writeRemoteRoles(userHome, roles); + } + + Node userProfile = UserJcrUtils.getUserProfile(session, username); + if (userProfile == null) { + String personPath = generateUserPath( + ArgeoJcrConstants.PEOPLE_BASE_PATH, username); + Node personBase = JcrUtils.mkdirs(session, personPath); + userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, + true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, + true); + userProfile.setProperty( + ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); + session.save(); + + JcrUtils.clearAccessControList(session, userProfile.getPath(), + username); + JcrUtils.addPrivilege(session, userProfile.getPath(), username, + Privilege.JCR_READ); + + VersionManager versionManager = session.getWorkspace() + .getVersionManager(); + if (versionManager.isCheckedOut(userProfile.getPath())) + versionManager.checkin(userProfile.getPath()); + + } + + // Remote roles + if (roles != null) { + writeRemoteRoles(userProfile, roles); + } + return userProfile; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot sync node security model for " + + username, e); + } + } + + /** Generate path for a new user home */ + protected String generateUserPath(String base, String username) { + int atIndex = username.indexOf('@'); + if (atIndex > 0) { + String domain = username.substring(0, atIndex); + String name = username.substring(atIndex + 1); + return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/' + + name; + } else if (atIndex == 0 || atIndex == (username.length() - 1)) { + throw new ArgeoException("Unsupported username " + username); + } else { + return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/' + + username; + } + } + + /** Write remote roles used by remote access in the home directory */ + protected void writeRemoteRoles(Node userHome, List roles) + throws RepositoryException { + boolean writeRoles = false; + if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] remoteRoles = userHome.getProperty( + ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + if (remoteRoles.length != roles.size()) + writeRoles = true; + else + for (int i = 0; i < remoteRoles.length; i++) + if (!remoteRoles[i].getString().equals(roles.get(i))) + writeRoles = true; + } else + writeRoles = true; + + if (writeRoles) { + userHome.getSession().getWorkspace().getVersionManager() + .checkout(userHome.getPath()); + String[] roleIds = roles.toArray(new String[roles.size()]); + userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds); + JcrUtils.updateLastModified(userHome); + userHome.getSession().save(); + userHome.getSession().getWorkspace().getVersionManager() + .checkin(userHome.getPath()); + if (log.isDebugEnabled()) + log.debug("Wrote remote roles " + roles + " for " + + userHome.getProperty(ArgeoNames.ARGEO_USER_ID)); + } + + } + + public void setHomeBasePath(String homeBasePath) { + this.homeBasePath = homeBasePath; + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java b/org.argeo.security.core/src/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java new file mode 100644 index 000000000..37dc98676 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/jcr/rememberme/JcrPersistentTokenRepository.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr.rememberme; + +import java.util.Date; + +import org.springframework.security.ui.rememberme.PersistentRememberMeToken; +import org.springframework.security.ui.rememberme.PersistentTokenRepository; + +public class JcrPersistentTokenRepository implements PersistentTokenRepository { + + public void createNewToken(PersistentRememberMeToken token) { + // TODO Auto-generated method stub + + } + + public void updateToken(String series, String tokenValue, Date lastUsed) { + // TODO Auto-generated method stub + + } + + public PersistentRememberMeToken getTokenForSeries(String seriesId) { + // TODO Auto-generated method stub + return null; + } + + public void removeUserTokens(String username) { + // TODO Auto-generated method stub + + } + +} diff --git a/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java b/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java new file mode 100644 index 000000000..1da985703 --- /dev/null +++ b/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.log4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.spi.LoggingEvent; +import org.argeo.ArgeoException; +import org.argeo.ArgeoLogListener; +import org.argeo.ArgeoLogger; +import org.argeo.security.SecurityUtils; + +/** Not meant to be used directly in standard log4j config */ +public class SecureLogger implements ArgeoLogger { + + private Boolean disabled = false; + + private String level = null; + + private Level log4jLevel = null; + // private Layout layout; + + private Properties configuration; + + private AppenderImpl appender; + + private final List everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + + /** Marker to prevent stack overflow */ + private ThreadLocal dispatching = new ThreadLocal() { + + @Override + protected Boolean initialValue() { + return false; + } + }; + + public void init() { + try { + events = new LinkedBlockingQueue(); + + // if (layout != null) + // setLayout(layout); + // else + // setLayout(new PatternLayout(pattern)); + appender = new AppenderImpl(); + reloadConfiguration(); + Logger.getRootLogger().addAppender(appender); + + logDispatcherThread = new LogDispatcherThread(); + logDispatcherThread.start(); + } catch (Exception e) { + throw new ArgeoException("Cannot initialize log4j"); + } + } + + public void destroy() throws Exception { + Logger.getRootLogger().removeAppender(appender); + allUsersListeners.clear(); + for (List lst : userListeners.values()) + lst.clear(); + userListeners.clear(); + + events.clear(); + events = null; + logDispatcherThread.interrupt(); + } + + // public void setLayout(Layout layout) { + // this.layout = layout; + // } + + public synchronized void register(ArgeoLogListener listener, + Integer numberOfPreviousEvents) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (username == null) + throw new ArgeoException( + "Only authenticated users can register a log listener"); + + if (!userListeners.containsKey(username)) { + List lst = Collections + .synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List lastEvents = logDispatcherThread.getLastEvents(username, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + dispatchEvent(listener, evt); + } + + public synchronized void registerForAll(ArgeoLogListener listener, + Integer numberOfPreviousEvents, boolean everything) { + if (everything) + everythingListeners.add(listener); + else + allUsersListeners.add(listener); + List lastEvents = logDispatcherThread.getLastEvents(null, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + if (everything || evt.getUsername() != null) + dispatchEvent(listener, evt); + } + + public synchronized void unregister(ArgeoLogListener listener) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (!userListeners.containsKey(username)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + if (!userListeners.get(username).contains(listener)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + userListeners.get(username).remove(listener); + if (userListeners.get(username).isEmpty()) + userListeners.remove(username); + + } + + public synchronized void unregisterForAll(ArgeoLogListener listener) { + everythingListeners.remove(listener); + allUsersListeners.remove(listener); + } + + /** For development purpose, since using regular logging is not easy here */ + static void stdOut(Object obj) { + System.out.println(obj); + } + + // public void setPattern(String pattern) { + // this.pattern = pattern; + // } + + public void setDisabled(Boolean disabled) { + this.disabled = disabled; + } + + public void setLevel(String level) { + this.level = level; + } + + public void setConfiguration(Properties configuration) { + this.configuration = configuration; + } + + public void updateConfiguration(Properties configuration) { + setConfiguration(configuration); + reloadConfiguration(); + } + + public Properties getConfiguration() { + return configuration; + } + + /** Reloads configuration (if the configuration {@link Properties} is set) */ + protected void reloadConfiguration() { + if (configuration != null) { + LogManager.resetConfiguration(); + PropertyConfigurator.configure(configuration); + } + } + + protected synchronized void processLoggingEvent(LogEvent event) { + if (disabled) + return; + + if (dispatching.get()) + return; + + if (level != null && !level.trim().equals("")) { + if (log4jLevel == null || !log4jLevel.toString().equals(level)) + try { + log4jLevel = Level.toLevel(level); + } catch (Exception e) { + System.err + .println("Log4j level could not be set for level '" + + level + "', resetting it to null."); + e.printStackTrace(); + level = null; + } + + if (log4jLevel != null + && !event.getLoggingEvent().getLevel() + .isGreaterOrEqual(log4jLevel)) { + return; + } + } + + try { + // admin listeners + Iterator everythingIt = everythingListeners + .iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners + .iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator userIt = userListeners.get( + event.getUsername()).iterator(); + while (userIt.hasNext()) + dispatchEvent(userIt.next(), event); + } + } + } catch (Exception e) { + stdOut("Cannot process logging event"); + e.printStackTrace(); + } + } + + protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { + LoggingEvent event = evt.getLoggingEvent(); + logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event + .getLevel().toString(), event.getLoggerName(), event + .getThreadName(), event.getMessage(), event + .getThrowableStrRep()); + } + + private class AppenderImpl extends AppenderSkeleton { + public boolean requiresLayout() { + return false; + } + + public void close() { + } + + @Override + protected void append(LoggingEvent event) { + if (events != null) { + try { + String username = SecurityUtils.getCurrentThreadUsername(); + events.put(new LogEvent(username, event)); + } catch (InterruptedException e) { + // silent + } + } + } + + } + + private class LogDispatcherThread extends Thread { + /** encapsulated in order to simplify concurrency management */ + private LinkedList lastEvents = new LinkedList(); + + public LogDispatcherThread() { + super("Argeo Logging Dispatcher Thread"); + } + + public void run() { + while (events != null) { + try { + LogEvent loggingEvent = events.take(); + processLoggingEvent(loggingEvent); + addLastEvent(loggingEvent); + } catch (InterruptedException e) { + if (events == null) + return; + } + } + } + + protected synchronized void addLastEvent(LogEvent loggingEvent) { + if (lastEvents.size() >= maxLastEventsCount) + lastEvents.poll(); + lastEvents.add(loggingEvent); + } + + public synchronized List getLastEvents(String username, + Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator it = lastEvents.listIterator(lastEvents + .size()); + int count = 0; + while (it.hasPrevious() && (count < maxCount)) { + LogEvent evt = it.previous(); + if (username == null || username.equals(evt.getUsername())) { + evts.push(evt); + count++; + } + } + return evts; + } + } + + private class LogEvent { + private final String username; + private final LoggingEvent loggingEvent; + + public LogEvent(String username, LoggingEvent loggingEvent) { + super(); + this.username = username; + this.loggingEvent = loggingEvent; + } + + @Override + public int hashCode() { + return loggingEvent.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return loggingEvent.equals(obj); + } + + @Override + public String toString() { + return username + "@ " + loggingEvent.toString(); + } + + public String getUsername() { + return username; + } + + public LoggingEvent getLoggingEvent() { + return loggingEvent; + } + + } +} diff --git a/org.argeo.security.core/src/test/java/org/argeo/security/PasswordSandbox.java b/org.argeo.security.core/src/test/java/org/argeo/security/PasswordSandbox.java deleted file mode 100644 index d8a084607..000000000 --- a/org.argeo.security.core/src/test/java/org/argeo/security/PasswordSandbox.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security; - -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.Hex; -import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder; - -public class PasswordSandbox { - public static void main(String[] args) { - try { - // Tested password - String pwdPlain = "demo"; - - // Check Java generated values - LdapShaPasswordEncoder lspe = new LdapShaPasswordEncoder(); - String pwdLdapShaBase64 = lspe.encodePassword(pwdPlain, null); - System.out.println("pwdLdapShaBase64:\t\t" + pwdLdapShaBase64); - - String pwdShaBase64 = pwdLdapShaBase64.substring("{SHA}".length()); - System.out.println("pwdShaBase64:\t\t\t" + pwdShaBase64); - - byte[] pwdShaArray = Base64.decodeBase64(pwdShaBase64.getBytes()); - String pwdShaHex = new String(Hex.encodeHex(pwdShaArray)); - System.out.println("pwdShaHex:\t\t\t" + pwdShaHex); - - // Check that we can use JavaScript generated values in Hex - String jsShaHex = "89e495e7941cf9e40e6980d14a16bf023ccd4c91"; - System.out.println("jsShaHex:\t\t\t" + pwdShaHex); - System.out.println("pwdShaHex==jsShaHex:\t\t" - + (pwdShaHex.equals(jsShaHex))); - - byte[] jsShaArray = Hex.decodeHex(jsShaHex.toCharArray()); - String jsShaBase64 = new String(Base64.encodeBase64(jsShaArray)); - System.out.println("jsShaBase64:\t\t\t" + jsShaBase64); - System.out.println("pwdShaBase64==jsShaBase64:\t" - + (pwdShaBase64.equals(jsShaBase64))); - } catch (DecoderException e) { - e.printStackTrace(); - } - - } - -} \ No newline at end of file diff --git a/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java b/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java deleted file mode 100644 index 6973f5704..000000000 --- a/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.crypto; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.PBEParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.xml.bind.DatatypeConverter; - -import junit.framework.TestCase; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.StreamUtils; -import org.argeo.security.crypto.PasswordBasedEncryption; - -public class PasswordBasedEncryptionTest extends TestCase { - private final static Log log = LogFactory - .getLog(PasswordBasedEncryptionTest.class); - - public void testEncryptDecrypt() { - final String password = "test long password since they are safer"; - PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption( - password.toCharArray()); - String message = "Hello World!"; - log.info("Password:\t'" + password + "'"); - log.info("Message:\t'" + message + "'"); - byte[] encrypted = pbeEnc.encryptString(message); - log.info("Encrypted:\t'" - + DatatypeConverter.printBase64Binary(encrypted) + "'"); - PasswordBasedEncryption pbeDec = new PasswordBasedEncryption( - password.toCharArray()); - InputStream in = null; - in = new ByteArrayInputStream(encrypted); - String decrypted = pbeDec.decryptAsString(in); - log.info("Decrypted:\t'" + decrypted + "'"); - StreamUtils.closeQuietly(in); - assertEquals(message, decrypted); - } - - public void testPBEWithMD5AndDES() throws Exception { - String password = "test"; - String message = "Hello World!"; - - byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, - (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; - - int count = 1024; - - String cipherAlgorithm = "PBEWithMD5AndDES"; - String secretKeyAlgorithm = "PBEWithMD5AndDES"; - SecretKeyFactory keyFac = SecretKeyFactory - .getInstance(secretKeyAlgorithm); - PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); - PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count); - SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec); - Cipher ecipher = Cipher.getInstance(cipherAlgorithm); - ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec); - Cipher dcipher = Cipher.getInstance(cipherAlgorithm); - dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec); - - byte[] encrypted = ecipher.doFinal(message.getBytes()); - byte[] decrypted = dcipher.doFinal(encrypted); - assertEquals(message, new String(decrypted)); - - } - - public void testPBEWithSHA1AndAES() throws Exception { - String password = "test"; - String message = "Hello World!"; - - byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, - (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; - byte[] iv = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, - (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99, - (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, - (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 }; - - int count = 1024; - // int keyLength = 256; - int keyLength = 128; - - String cipherAlgorithm = "AES/CBC/PKCS5Padding"; - String secretKeyAlgorithm = "PBKDF2WithHmacSHA1"; - SecretKeyFactory keyFac = SecretKeyFactory - .getInstance(secretKeyAlgorithm); - PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, - count, keyLength); - SecretKey tmp = keyFac.generateSecret(pbeKeySpec); - SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - Cipher ecipher = Cipher.getInstance(cipherAlgorithm); - ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv)); - - // decrypt - keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm); - pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count, - keyLength); - tmp = keyFac.generateSecret(pbeKeySpec); - secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - // AlgorithmParameters params = ecipher.getParameters(); - // byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); - Cipher dcipher = Cipher.getInstance(cipherAlgorithm); - dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); - - byte[] encrypted = ecipher.doFinal(message.getBytes()); - byte[] decrypted = dcipher.doFinal(encrypted); - assertEquals(message, new String(decrypted)); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher); - cipherOut.write(message.getBytes()); - StreamUtils.closeQuietly(cipherOut); - byte[] enc = out.toByteArray(); - - ByteArrayInputStream in = new ByteArrayInputStream(enc); - CipherInputStream cipherIn = new CipherInputStream(in, dcipher); - ByteArrayOutputStream dec = new ByteArrayOutputStream(); - StreamUtils.copy(cipherIn, dec); - assertEquals(message, new String(dec.toByteArray())); - } -} diff --git a/org.argeo.security.core/src/test/resources/log4j.properties b/org.argeo.security.core/src/test/resources/log4j.properties deleted file mode 100644 index b33daa9db..000000000 --- a/org.argeo.security.core/src/test/resources/log4j.properties +++ /dev/null @@ -1,28 +0,0 @@ -log4j.rootLogger=WARN, console - -## Levels -log4j.logger.org.argeo=DEBUG - -log4j.logger.org.hibernate=WARN - -log4j.logger.org.springframework=WARN -#log4j.logger.org.springframework.web=DEBUG -#log4j.logger.org.springframework.jms=WARN -#log4j.logger.org.springframework.security=WARN - -log4j.logger.org.apache.activemq=WARN -log4j.logger.org.apache.activemq.transport=WARN -log4j.logger.org.apache.activemq.ActiveMQMessageConsumer=INFO -log4j.logger.org.apache.activemq.ActiveMQMessageProducer=INFO - -log4j.logger.org.apache.catalina=INFO -log4j.logger.org.apache.coyote=INFO -log4j.logger.org.apache.tomcat=INFO - -## Appenders -# console is set to be a ConsoleAppender. -log4j.appender.console=org.apache.log4j.ConsoleAppender - -# console uses PatternLayout. -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n diff --git a/org.argeo.security.core/src/test/resources/org/argeo/security/json/gandalf2.json b/org.argeo.security.core/src/test/resources/org/argeo/security/json/gandalf2.json deleted file mode 100644 index 73918229b..000000000 --- a/org.argeo.security.core/src/test/resources/org/argeo/security/json/gandalf2.json +++ /dev/null @@ -1 +0,0 @@ -{"roles":["ROLE_ADMIN","ROLE_USER"],"userNatures":[{"description":"Superuser","email":"admin@localhost","firstName":"Gandalf","lastName":"User","type":"org.argeo.security.nature.SimpleUserNature"},{"mobile":null,"telephoneNumber":null,"type":"org.argeo.security.nature.CoworkerNature"}],"username":"gandalf2","enabled":true,"password":"{SHA}ieSV55Qc+eQOaYDRSha/AjzNTJE=","authorities":[{"authority":"ROLE_ADMIN"},{"authority":"ROLE_USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true} \ No newline at end of file