Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a login() method to security support #270

Merged
merged 1 commit into from Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,9 @@
# Version 3.9.1 (2019-11-30)

* [new] Support for programmatic login through `SecuritySupport` interface (no need for Shiro-specific code anymore).
* [chg] Obtaining principals by type now honors inheritance (instead of returning principals of the exact specified type).
* [chg] Principals are no longer required to be serializable.

# Version 3.9.0 (2019-08-12)

* [new] Introduce the `diag` tool to manually write a diagnostic report to standard output or in a file.
Expand Down
Expand Up @@ -113,7 +113,7 @@ public boolean supports(AuthenticationToken token) {
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
Object authenticationCacheKey = super.getAuthenticationCacheKey(token);
if (authenticationCacheKey instanceof PrincipalProvider) {
return ((PrincipalProvider) authenticationCacheKey).getPrincipal();
return ((PrincipalProvider) authenticationCacheKey).get();
} else {
return authenticationCacheKey;
}
Expand All @@ -123,7 +123,7 @@ protected Object getAuthenticationCacheKey(AuthenticationToken token) {
protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
Object authenticationCacheKey = super.getAuthenticationCacheKey(principals);
if (authenticationCacheKey instanceof PrincipalProvider) {
return ((PrincipalProvider) authenticationCacheKey).getPrincipal();
return ((PrincipalProvider) authenticationCacheKey).get();
} else {
return authenticationCacheKey;
}
Expand All @@ -133,7 +133,7 @@ protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
Object primaryPrincipal = principals.getPrimaryPrincipal();
if (primaryPrincipal instanceof PrincipalProvider) {
return ((PrincipalProvider) primaryPrincipal).getPrincipal();
return ((PrincipalProvider) primaryPrincipal).get();
} else {
return primaryPrincipal;
}
Expand Down
Expand Up @@ -5,9 +5,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.seedstack.seed.security.internal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -18,25 +18,31 @@
import org.apache.commons.lang.ArrayUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.seedstack.seed.security.AuthenticationException;
import org.seedstack.seed.security.AuthenticationToken;
import org.seedstack.seed.security.AuthorizationException;
import org.seedstack.seed.security.Role;
import org.seedstack.seed.security.Scope;
import org.seedstack.seed.security.SecuritySupport;
import org.seedstack.seed.security.SimpleScope;
import org.seedstack.seed.security.internal.authorization.ScopePermission;
import org.seedstack.seed.security.internal.authorization.SeedAuthorizationInfo;
import org.seedstack.seed.security.internal.realms.AuthenticationTokenWrapper;
import org.seedstack.seed.security.principals.PrincipalProvider;
import org.seedstack.seed.security.principals.Principals;
import org.seedstack.seed.security.principals.SimplePrincipalProvider;

class ShiroSecuritySupport implements SecuritySupport {

@Inject
private Set<Realm> realms;
@Inject
private SecurityManager securityManager;

@Override
public PrincipalProvider<?> getIdentityPrincipal() {
Expand All @@ -63,7 +69,12 @@ public Collection<PrincipalProvider<?>> getOtherPrincipals() {
}

@Override
public <T extends Serializable> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass) {
public <T> PrincipalProvider<T> getPrincipalByType(Class<T> principalClass) {
return Principals.getOnePrincipalByType(getOtherPrincipals(), principalClass);
}

@Override
public <T> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass) {
return Principals.getPrincipalsByType(getOtherPrincipals(), principalClass);
}

Expand Down Expand Up @@ -206,6 +217,25 @@ public void checkRoles(String... roleIdentifiers) {
}
}

@Override
public void login(AuthenticationToken authenticationToken) {
SecurityManager alreadyBoundSecurityManager = ThreadContext.getSecurityManager();
try {
if (alreadyBoundSecurityManager == null) {
ThreadContext.bind(securityManager);
}
Subject currentSubject = SecurityUtils.getSubject();
currentSubject.login(new AuthenticationTokenWrapper(authenticationToken));
} catch (org.apache.shiro.authc.AuthenticationException e) {
throw new AuthenticationException("Unable to login subject with provided credentials " + authenticationToken
.getPrincipal(), e);
} finally {
if (alreadyBoundSecurityManager == null) {
ThreadContext.unbindSecurityManager();
}
}
}

@Override
public void logout() {
SecurityUtils.getSubject().logout();
Expand Down
Expand Up @@ -57,7 +57,7 @@ protected ConfigurationRealm(@Named("ConfigurationRealm-role-mapping") RoleMappi
@Override
public Set<String> getRealmRoles(PrincipalProvider<?> identityPrincipal,
Collection<PrincipalProvider<?>> otherPrincipals) {
ConfigurationUser user = findUser(identityPrincipal.getPrincipal().toString());
ConfigurationUser user = findUser(identityPrincipal.get().toString());
if (user != null) {
return user.roles;
}
Expand Down
Expand Up @@ -122,7 +122,7 @@ public Set<String> getRealmRoles(PrincipalProvider<?> identityPrincipal,
if (certificatePrincipals.isEmpty()) {
return Collections.emptySet();
}
X509Certificate[] certificates = certificatePrincipals.iterator().next().getPrincipal();
X509Certificate[] certificates = certificatePrincipals.iterator().next().get();
for (X509Certificate certificate : certificates) {
String dn = certificate.getIssuerX500Principal().getName(X500Principal.RFC2253);
LdapName ln;
Expand Down
Expand Up @@ -5,6 +5,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.seedstack.seed.security.internal.realms;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -61,7 +62,7 @@ public void getRealmRoles_returns_empty_if_user_unknown() {
public void getAuthenticationInfo_nominal() {
UsernamePasswordToken token = new UsernamePasswordToken(USERNAME, PASSWORD);
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);
assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(USERNAME);
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(USERNAME);
}

@Test(expected = IncorrectCredentialsException.class)
Expand Down
Expand Up @@ -56,10 +56,10 @@ public void getAuthenticationInfoShouldReturnAuthenticationInfo() {
when(x509Certificate.getSubjectX500Principal()).thenReturn(x500Principal);
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);

assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(id);
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(id);
PrincipalProvider<X509Certificate[]> x509pp = Principals.getOnePrincipalByType(authInfo.getOtherPrincipals(),
X509Certificate[].class);
assertThat(x509pp.getPrincipal()[0]).isEqualTo(x509Certificate);
assertThat(x509pp.get()[0]).isEqualTo(x509Certificate);
}

@Test(expected = UnsupportedTokenException.class)
Expand All @@ -78,7 +78,7 @@ public void getAuthenticationInfoNoUid() {
X500Principal x500Principal = new X500Principal("CN=John Doe, OU=SI, O=PSA");
when(x509Certificate.getSubjectX500Principal()).thenReturn(x500Principal);
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);
assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(x500Principal);
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(x500Principal);
}

@Test
Expand Down
Expand Up @@ -5,9 +5,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.seedstack.seed.security;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import org.seedstack.seed.security.principals.PrincipalProvider;
Expand All @@ -34,21 +34,25 @@ public interface SecuritySupport {
Collection<PrincipalProvider<?>> getOtherPrincipals();

/**
* Gets all the PrincipalProviders corresponding to a type of PrincipalProvider.<br>
* <br>
* For example, you can use this method to get the LDAPUser by calling :<br>
* {@code getPrincipalsByType(LDAPUser.class)}.<br>
* <br>
* Then on the first element of the collection : <br>
* {@code LDAPUser user =
* ldapUserPrincipalProvider.getPrincipal()}.
* Gets the first {@link PrincipalProvider} that provide a principal assignable to the specified type.
*
* @param <T> type of the principal
* @param principalClass the Principal type, not null
* @return The first first {@link PrincipalProvider} that provide a principal assignable to the specified type.
* Null if none found.
* @see org.seedstack.seed.security.principals.Principals#getOnePrincipalByType(Collection, Class)
*/
<T> PrincipalProvider<T> getPrincipalByType(Class<T> principalClass);

/**
* Gets all {@link PrincipalProvider}s that provide a principal assignable to the specified type.
*
* @param <T> type of the principal
* @param principalClass the Principal type, not null
* @return A collection of the user's PrincipalProviders of type principalProviderClass. Not null.
* @see org.seedstack.seed.security.principals.Principals#getPrincipalsByType(Collection, Class)
*/
<T extends Serializable> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass);
<T> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass);

/**
* Gets the user's SimplePrincipalProviders.<br>
Expand Down Expand Up @@ -231,26 +235,29 @@ public interface SecuritySupport {
*/
Set<SimpleScope> getSimpleScopes();

/**
* Explicitly authenticates a subject with its authentication token.
* <h3>Web Environment Warning</h3> In a Web application, this is typically handled by a security Web filter, so
* calling this method should be avoided in such environments.
*/
void login(AuthenticationToken authenticationToken);

/**
* Logs out the connected user and invalidates and/or removes any associated entities, such as a Session and
* authorization data. After this method
* is called, the user is considered 'anonymous' and may continue to be used for another log-in if desired.
* <h3>Web Environment Warning</h3>
* Calling this method in web environments will usually remove any associated session cookie as part of session
* invalidation. Because cookies are
* part of the HTTP header, and headers can only be set before the response body (html, image, etc) is sent, this
* method in web environments must
* be called before <em>any</em> content has been rendered.
* <p>
* The typical approach most applications use in this scenario is to redirect the user to a different location (e
* .g. home page) immediately after
* calling this method. This is an effect of the HTTP protocol itself and not a reflection of the implementation.
* authorization data. After this method is called, the user is considered 'anonymous' and may continue to be
* used for another log-in if desired. <h3>Web Environment Warning</h3> Calling this method in web environments
* will usually remove any associated session cookie as part of session invalidation. Because cookies are part of
* the HTTP header, and headers can only be set before the response body (html, image, etc) is sent, this method
* in web environments must be called before <em>any</em> content has been rendered. <p> The typical approach
* most applications use in this scenario is to redirect the user to a different location (e .g. home page)
* immediately after calling this method. This is an effect of the HTTP protocol itself and not a reflection of
* the implementation.
*/
void logout();

/**
* Check if the current user is authenticated.
*
* <p>
* Authenticated on Shiro means that subject has successfully logged in on the current session
*
* @return true if authenticated, false otherwise.
Expand Down
Expand Up @@ -5,6 +5,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.seedstack.seed.security.principals;

import java.io.Serializable;
Expand All @@ -14,12 +15,24 @@
*
* @param <T> the type of the object provided by the principal
*/
public interface PrincipalProvider<T extends Serializable> {
public interface PrincipalProvider<T> {
/**
* For compatibility purposes.
*
* @param <X> a serializable type
* @return the object cast as X.
* @deprecated
*/
@Deprecated
@SuppressWarnings("unchecked")
default <X extends Serializable> X getPrincipal() {
return (X) get();
}

/**
* Gives the enclosed principal.
*
* @return the object enclosed in the principal
*/
T getPrincipal();
T get();
}