diff --git a/CHANGELOG.md b/CHANGELOG.md index cb79ec6a..3fe14b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Changed +- Change Crowd integration from SOAP to REST ([#740](https://github.com/opendevstack/ods-provisioning-app/issues/740)) + ## [4.1] - 2022-11-17 ### Added @@ -12,7 +15,6 @@ - API DELETE*: wrong jenkins run job (lastExecutionJobs) returned ([#710](https://github.com/opendevstack/ods-provisioning-app/issues/710)) - Missing bitbucket repository description on repository creation event ([#713](https://github.com/opendevstack/ods-provisioning-app/issues/713)) - Fix problem assigning admin permissions to bitbucket repositories ([#700](https://github.com/opendevstack/ods-provisioning-app/pull/700)) -- Fix problem assigning admin permissions to bitbucket repositories ([#700](https://github.com/opendevstack/ods-provisioning-app/pull/700)) - Fixes jcenter repository no more available. ([#737](https://github.com/opendevstack/ods-provisioning-app/pull/737)) - Fixes could not find com.atlassian.platform:platform:3.5.2 ([#738](https://github.com/opendevstack/ods-provisioning-app/pull/738)) - ODS AMI E2E quickstarter prov app fails due to no nexus equal false ([#730](https://github.com/opendevstack/ods-provisioning-app/pull/730)) diff --git a/build.gradle b/build.gradle index 229be78f..a1548945 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,9 @@ repositories { maven() { url "https://packages.atlassian.com/maven-public/" } + maven() { + url 'http://maven.imagej.net/content/repositories/public/' + } } } @@ -135,21 +138,11 @@ dependencies { transitive = true } implementation('javax.validation:validation-api:2.0.1.Final') - implementation('com.atlassian.crowd:crowd-integration-springsecurity:1000.82.0') { - exclude(group: 'commons-httpclient') - exclude(group: 'org.apache.ws.commons', module: 'XmlSchema') - // Explicitly excludes vulnerable versions - exclude(group: 'org.apache.struts', module: 'struts2-core') - exclude(group: 'org.apache.struts.xwork', module: 'xwork-core') - exclude(group: 'commons-collections', module: 'commons-collections') - exclude(group: 'commons-fileupload', module: 'commons-fileupload') - exclude(group: 'com.fasterxml.jackson.core', module: 'jackson-databind') - exclude(group: 'org.aspectj', module: 'aspectjweaver') - exclude(group: 'com.google.guava', module: 'guava') - } + implementation('com.atlassian.crowd:crowd-integration-springsecurity:5.1.3') + implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' + implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '2.3.1' + implementation group: 'xerces', name: 'xercesImpl', version: '2.9.1' - // latest version of excluded libs: refactor this when upgrading to new 'com.atlassian.crowd:crowd-integration-springsecurity' - implementation('com.google.guava:guava:30.0-jre') testImplementation('com.github.tomakehurst:wiremock-jre8:2.32.0') } @@ -221,7 +214,8 @@ task npmBuild(type:Exec) { commandLine 'npm', 'run', 'build' } -bootRun.dependsOn npmBuild +// Uncomment to compile npm front if frontend.spa.enabled=true in .properties +//bootRun.dependsOn npmBuild configurations.all { resolutionStrategy.eachDependency { diff --git a/client/package-lock.json b/client/package-lock.json index 4e8e9692..b45adda1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "client", "version": "0.0.0", "dependencies": { "@angular/animations": "~12.1.2", @@ -29173,7 +29174,9 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "dev": true, - "requires": {} + "requires": { + "ajv": "^8.0.0" + } }, "alphanum-sort": { "version": "1.0.2", diff --git a/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter.java b/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter.java index 75471d0a..1c1869c7 100644 --- a/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter.java +++ b/src/main/java/org/opendevstack/provision/adapter/IODSAuthnzAdapter.java @@ -14,6 +14,9 @@ package org.opendevstack.provision.adapter; +import com.atlassian.crowd.exception.ApplicationPermissionException; +import com.atlassian.crowd.exception.InvalidAuthenticationException; +import com.atlassian.crowd.exception.OperationFailedException; import org.opendevstack.provision.adapter.exception.IdMgmtException; /** Interface to wrap all (current) user based identity calls */ @@ -42,6 +45,10 @@ public interface IODSAuthnzAdapter { /** Get the currently logged' in user's email */ public String getUserEmail(); + void invalidate(String token) + throws InvalidAuthenticationException, OperationFailedException, + ApplicationPermissionException; + /** * Invalidate the currently logged' in identity * diff --git a/src/main/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManager.java b/src/main/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManager.java deleted file mode 100644 index a93020e4..00000000 --- a/src/main/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManager.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * 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.opendevstack.provision.authentication; - -import com.atlassian.crowd.exception.InvalidAuthenticationException; -import com.atlassian.crowd.exception.InvalidAuthorizationTokenException; -import com.atlassian.crowd.exception.UserNotFoundException; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.cache.CachingGroupMembershipManager; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import java.rmi.RemoteException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Simple extension of CachingGroupMembershipManager to fix - * https://github.com/opendevstack/ods-provisioning-app/issues/106 - */ -public class SimpleCachingGroupMembershipManager extends CachingGroupMembershipManager { - /** security server restClient */ - private final SecurityServerClient securityServerClient; - /** cache */ - private final BasicCache basicCache; - - private static final Logger logger = - LoggerFactory.getLogger(SimpleCachingGroupMembershipManager.class); - - public SimpleCachingGroupMembershipManager( - SecurityServerClient securityServerClient, - UserManager userManager, - GroupManager groupManager, - BasicCache basicCache) { - super(securityServerClient, userManager, groupManager, basicCache); - this.securityServerClient = securityServerClient; - this.basicCache = basicCache; - } - - @Override - public List getMemberships(String user) - throws RemoteException, InvalidAuthorizationTokenException, InvalidAuthenticationException, - UserNotFoundException { - List groupsForUser = basicCache.getAllMemberships(user); - if (groupsForUser == null || groupsForUser.isEmpty()) { - long startTime = System.currentTimeMillis(); - String[] groupMemberships = securityServerClient.findGroupMemberships(user); - - if (groupMemberships == null) { - return new ArrayList<>(); - } - - for (String group : groupMemberships) { - basicCache.addGroupToUser(user, group); - logger.debug("add group/user to cache ({} / {})", user, group); - } - logger.debug("add to cache ({}) took: {} ms", user, (System.currentTimeMillis() - startTime)); - - return new ArrayList<>(Arrays.asList(groupMemberships)); - } else { - long startTime = System.currentTimeMillis(); - for (String group : groupsForUser) { - logger.debug("retrieve from cache ({} / {})", user, group); - } - logger.debug( - "retrieve from cache ({}) took: {} ms", user, (System.currentTimeMillis() - startTime)); - return groupsForUser; - } - } -} diff --git a/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapter.java b/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapter.java new file mode 100644 index 00000000..1d5ebedd --- /dev/null +++ b/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapter.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * 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.opendevstack.provision.authentication.crowd; + +import com.atlassian.crowd.exception.*; +import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; +import com.atlassian.crowd.model.group.ImmutableGroup; +import com.atlassian.crowd.service.client.CrowdClient; +import com.google.common.base.Preconditions; +import java.rmi.RemoteException; +import org.opendevstack.provision.adapter.IODSAuthnzAdapter; +import org.opendevstack.provision.adapter.exception.IdMgmtException; +import org.opendevstack.provision.authentication.SessionAwarePasswordHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +/** Custom Authentication manager to integrate the password storing for authentication */ +@Component +@ConditionalOnProperty(name = "provision.auth.provider", havingValue = "crowd") +public class CrowdAuthenticationAdapter implements IODSAuthnzAdapter { + private static final Logger logger = LoggerFactory.getLogger(CrowdAuthenticationAdapter.class); + private CrowdClient crowdClient; + + @Autowired private SessionAwarePasswordHolder userPassword; + + /** + * Constructor with secure SOAP restClient for crowd authentication + * + * @param crowdClient + */ + public CrowdAuthenticationAdapter(CrowdClient crowdClient) { + this.crowdClient = crowdClient; + } + + /** @see IODSAuthnzAdapter#getUserPassword() */ + public String getUserPassword() { + return userPassword.getPassword(); + } + + /** @see IODSAuthnzAdapter#getUserName() */ + public String getUserName() { + return userPassword.getUsername(); + } + + /** @see IODSAuthnzAdapter#getToken() */ + public String getToken() { + return userPassword.getToken(); + } + + /** @see IODSAuthnzAdapter#getUserEmail() () */ + public String getUserEmail() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth == null) { + return null; + } + + if (!(auth.getPrincipal() instanceof CrowdUserDetails)) { + return null; + } + + CrowdUserDetails userDetails = (CrowdUserDetails) auth.getPrincipal(); + + return userDetails.getEmail(); + } + + /** open for testing */ + public void setUserPassword(String userPassword) { + this.userPassword.setPassword(userPassword); + } + + /** open for testing */ + public void setUserName(String userName) { + this.userPassword.setUsername(userName); + } + + /** + * Invalidate a session based on a user#s token + * + * @param token the users token + * @throws RemoteException + * @throws InvalidAuthorizationTokenException + * @throws InvalidAuthenticationException + */ + @Override + public void invalidate(String token) + throws InvalidAuthenticationException, OperationFailedException, + ApplicationPermissionException { + Preconditions.checkNotNull(token); + this.getCrowdClient().invalidateSSOToken(token); + userPassword.clear(); + } + + @Override + public void invalidateIdentity() + throws OperationFailedException, ApplicationPermissionException, + InvalidAuthenticationException { + invalidate(getToken()); + } + + @Override + public boolean existsGroupWithName(String groupName) { + try { + crowdClient.getGroup(groupName); + return true; + } catch (Exception exception) { + if (!(exception instanceof GroupNotFoundException)) { + logger.error("GroupFind call failed with:", exception); + } + return false; + } + } + + @Override + public boolean existPrincipalWithName(String userName) { + try { + crowdClient.getUser(userName); + return true; + } catch (Exception exception) { + if (!(exception instanceof UserNotFoundException)) { + logger.error("UserFind call failed with:", exception); + } + return false; + } + } + + @Override + public String addGroup(String groupName) throws IdMgmtException { + try { + crowdClient.addGroup(ImmutableGroup.builder(groupName).build()); + return groupName; + } catch (Exception ex) { + logger.error("Could not create group {}, error: {}", groupName, ex.getMessage(), ex); + throw new IdMgmtException(ex); + } + } + + @Override + public String getAdapterApiUri() { + throw new UnsupportedOperationException("not supported in oauth2 or basic auth authentication"); + } + + /** + * get the internal secure restClient + * + * @return the secure restClient for crowd connect + */ + public CrowdClient getCrowdClient() { + return this.crowdClient; + } + + /** + * Set the secure restClient for injection in tests + * + * @param crowdClient + */ + void setCrowdClient(CrowdClient crowdClient) { + this.crowdClient = crowdClient; + } +} diff --git a/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManager.java b/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManager.java deleted file mode 100644 index 6c83d1d6..00000000 --- a/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManager.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * 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.opendevstack.provision.authentication.crowd; - -import com.atlassian.crowd.exception.ApplicationAccessDeniedException; -import com.atlassian.crowd.exception.ExpiredCredentialException; -import com.atlassian.crowd.exception.GroupNotFoundException; -import com.atlassian.crowd.exception.InactiveAccountException; -import com.atlassian.crowd.exception.InvalidAuthenticationException; -import com.atlassian.crowd.exception.InvalidAuthorizationTokenException; -import com.atlassian.crowd.integration.soap.SOAPGroup; -import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; -import com.atlassian.crowd.model.authentication.UserAuthenticationContext; -import com.atlassian.crowd.model.authentication.ValidationFactor; -import com.atlassian.crowd.service.AuthenticationManager; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import com.google.common.base.Preconditions; -import java.rmi.RemoteException; -import org.opendevstack.provision.adapter.IODSAuthnzAdapter; -import org.opendevstack.provision.adapter.exception.IdMgmtException; -import org.opendevstack.provision.authentication.SessionAwarePasswordHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Component; - -/** Custom Authentication manager to integrate the password storing for authentication */ -@Component -@ConditionalOnProperty(name = "provision.auth.provider", havingValue = "crowd") -public class CrowdAuthenticationManager implements AuthenticationManager, IODSAuthnzAdapter { - - private static final Logger logger = LoggerFactory.getLogger(CrowdAuthenticationManager.class); - private SecurityServerClient securityServerClient; - - @Autowired private SessionAwarePasswordHolder userPassword; - - /** - * Constructor with secure SOAP restClient for crowd authentication - * - * @param securityServerClient - */ - public CrowdAuthenticationManager(SecurityServerClient securityServerClient) { - this.securityServerClient = securityServerClient; - } - - /** @see IODSAuthnzAdapter#getUserPassword() */ - public String getUserPassword() { - return userPassword.getPassword(); - } - - /** @see IODSAuthnzAdapter#getUserName() */ - public String getUserName() { - return userPassword.getUsername(); - } - - /** @see IODSAuthnzAdapter#getToken() */ - public String getToken() { - return userPassword.getToken(); - } - - /** @see IODSAuthnzAdapter#getUserEmail() () */ - public String getUserEmail() { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - - if (auth == null) { - return null; - } - - if (!(auth.getPrincipal() instanceof CrowdUserDetails)) { - return null; - } - - CrowdUserDetails userDetails = (CrowdUserDetails) auth.getPrincipal(); - - return userDetails.getEmail(); - } - - /** open for testing */ - public void setUserPassword(String userPassword) { - this.userPassword.setPassword(userPassword); - } - - /** open for testing */ - public void setUserName(String userName) { - this.userPassword.setUsername(userName); - } - - /** - * specific authentication method implementation for crowd integration - * - * @param authenticationContext the auth context passed from spring - * @return the user's token - * @throws RemoteException - * @throws InvalidAuthorizationTokenException - * @throws InvalidAuthenticationException - * @throws InactiveAccountException - * @throws ApplicationAccessDeniedException - * @throws ExpiredCredentialException - */ - @Override - public String authenticate(UserAuthenticationContext authenticationContext) - throws RemoteException, InvalidAuthorizationTokenException, InvalidAuthenticationException, - InactiveAccountException, ApplicationAccessDeniedException, ExpiredCredentialException { - Preconditions.checkNotNull(authenticationContext); - - if (authenticationContext.getApplication() == null) { - authenticationContext.setApplication( - this.getSecurityServerClient().getSoapClientProperties().getApplicationName()); - } - - String token = this.getSecurityServerClient().authenticatePrincipal(authenticationContext); - userPassword.setToken(token); - userPassword.setUsername(authenticationContext.getName()); - userPassword.setPassword(authenticationContext.getCredential().getCredential()); - return token; - } - - /** - * authenticate via crowd token - * - * @param authenticationContext - * @return - * @throws ApplicationAccessDeniedException - * @throws InvalidAuthenticationException - * @throws InvalidAuthorizationTokenException - * @throws InactiveAccountException - * @throws RemoteException - */ - @Override - public String authenticateWithoutValidatingPassword( - UserAuthenticationContext authenticationContext) - throws ApplicationAccessDeniedException, InvalidAuthenticationException, - InvalidAuthorizationTokenException, InactiveAccountException, RemoteException { - Preconditions.checkNotNull(authenticationContext); - return this.getSecurityServerClient() - .createPrincipalToken( - authenticationContext.getName(), authenticationContext.getValidationFactors()); - } - - /** - * simple authentication with username and password - * - * @param username users username - * @param password users password - * @return the users token - * @throws RemoteException - * @throws InvalidAuthorizationTokenException - * @throws InvalidAuthenticationException - * @throws InactiveAccountException - * @throws ApplicationAccessDeniedException - * @throws ExpiredCredentialException - */ - @Override - public String authenticate(String username, String password) - throws RemoteException, InvalidAuthorizationTokenException, InvalidAuthenticationException, - InactiveAccountException, ApplicationAccessDeniedException, ExpiredCredentialException { - - Preconditions.checkNotNull(username); - Preconditions.checkNotNull(password); - String token = this.getSecurityServerClient().authenticatePrincipalSimple(username, password); - userPassword.setToken(token); - userPassword.setUsername(username); - userPassword.setPassword(password); - return token; - } - - /** - * proof if user is authenticated - * - * @param token the users token - * @param validationFactors and additional auth factors, eg. IP - * @return true in case the token is valid - * @throws RemoteException - * @throws InvalidAuthorizationTokenException - * @throws ApplicationAccessDeniedException - * @throws InvalidAuthenticationException - */ - @Override - public boolean isAuthenticated(String token, ValidationFactor[] validationFactors) - throws RemoteException, InvalidAuthorizationTokenException, ApplicationAccessDeniedException, - InvalidAuthenticationException { - Preconditions.checkNotNull(token); - userPassword.setToken(token); - return this.getSecurityServerClient().isValidToken(token, validationFactors); - } - - /** - * Invalidate a session based on a user#s token - * - * @param token the users token - * @throws RemoteException - * @throws InvalidAuthorizationTokenException - * @throws InvalidAuthenticationException - */ - @Override - public void invalidate(String token) - throws RemoteException, InvalidAuthorizationTokenException, InvalidAuthenticationException { - Preconditions.checkNotNull(token); - this.getSecurityServerClient().invalidateToken(token); - userPassword.clear(); - } - - @Override - public void invalidateIdentity() throws Exception { - invalidate(getToken()); - } - - /** - * get the internal secure restClient - * - * @return the secure restClient for crowd connect - */ - @Override - public SecurityServerClient getSecurityServerClient() { - return this.securityServerClient; - } - - @Override - public boolean existsGroupWithName(String groupName) { - try { - securityServerClient.findGroupByName(groupName); - return true; - } catch (Exception exception) { - if (!(exception instanceof GroupNotFoundException)) { - logger.error("GroupFind call failed with:", exception); - } - return false; - } - } - - @Override - public boolean existPrincipalWithName(String userName) { - try { - getSecurityServerClient().findPrincipalByName(userName); - return true; - } catch (Exception exception) { - if (!(exception instanceof UsernameNotFoundException)) { - logger.error("UserFind call failed with:", exception); - } - return false; - } - } - - @Override - public String addGroup(String groupName) throws IdMgmtException { - try { - String name = - securityServerClient.addGroup(new SOAPGroup(groupName, new String[] {})).getName(); - return name; - } catch (Exception ex) { - logger.error("Could not create group {}, error: {}", groupName, ex.getMessage(), ex); - throw new IdMgmtException(ex); - } - } - - @Override - public String getAdapterApiUri() { - return securityServerClient.getSoapClientProperties().getBaseURL(); - } - - /** - * Set the secure restClient for injection in tests - * - * @param securityServerClient - */ - void setSecurityServerClient(SecurityServerClient securityServerClient) { - this.securityServerClient = securityServerClient; - } -} diff --git a/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdSecurityConfiguration.java b/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdSecurityConfiguration.java index 1041cc0f..c3c581cd 100644 --- a/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdSecurityConfiguration.java +++ b/src/main/java/org/opendevstack/provision/authentication/crowd/CrowdSecurityConfiguration.java @@ -14,35 +14,37 @@ package org.opendevstack.provision.authentication.crowd; -import com.atlassian.crowd.integration.http.HttpAuthenticator; -import com.atlassian.crowd.integration.http.HttpAuthenticatorImpl; -import com.atlassian.crowd.integration.springsecurity.CrowdLogoutHandler; -import com.atlassian.crowd.integration.springsecurity.RemoteCrowdAuthenticationProvider; -import com.atlassian.crowd.integration.springsecurity.UsernameStoringAuthenticationFailureHandler; +import com.atlassian.crowd.embedded.api.PasswordCredential; +import com.atlassian.crowd.exception.*; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticator; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticatorImpl; +import com.atlassian.crowd.integration.http.util.CrowdHttpTokenHelper; +import com.atlassian.crowd.integration.http.util.CrowdHttpTokenHelperImpl; +import com.atlassian.crowd.integration.http.util.CrowdHttpValidationFactorExtractor; +import com.atlassian.crowd.integration.http.util.CrowdHttpValidationFactorExtractorImpl; +import com.atlassian.crowd.integration.rest.service.factory.RestCrowdClientFactory; +import com.atlassian.crowd.integration.springsecurity.*; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsService; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl; -import com.atlassian.crowd.service.AuthenticationManager; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.cache.CacheImpl; -import com.atlassian.crowd.service.cache.CachingGroupManager; -import com.atlassian.crowd.service.cache.CachingUserManager; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import com.atlassian.crowd.service.soap.client.SecurityServerClientImpl; -import com.atlassian.crowd.service.soap.client.SoapClientPropertiesImpl; +import com.atlassian.crowd.model.authentication.UserAuthenticationContext; +import com.atlassian.crowd.model.authentication.ValidationFactor; +import com.atlassian.crowd.service.client.ClientProperties; +import com.atlassian.crowd.service.client.ClientPropertiesImpl; +import com.atlassian.crowd.service.client.CrowdClient; import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSessionListener; -import net.sf.ehcache.CacheManager; import org.jetbrains.annotations.NotNull; import org.opendevstack.provision.authentication.ProvAppHttpSessionListener; +import org.opendevstack.provision.authentication.SessionAwarePasswordHolder; import org.opendevstack.provision.authentication.filter.SSOAuthProcessingFilter; import org.opendevstack.provision.authentication.filter.SSOAuthProcessingFilterBasicAuthHandler; import org.opendevstack.provision.authentication.filter.SSOAuthProcessingFilterBasicAuthStrategy; @@ -52,16 +54,19 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; +import org.springframework.dao.DataAccessException; import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.*; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -100,6 +105,8 @@ public class CrowdSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired(required = false) private BasicAuthenticationEntryPoint basicAuthEntryPoint; + @Autowired private SessionAwarePasswordHolder userPassword; + @Override protected void configure(HttpSecurity http) throws Exception { @@ -147,7 +154,7 @@ protected void configure(HttpSecurity http) throws Exception { .and(); } - public Properties getProps() throws IOException { + public ClientProperties getProps() throws IOException { Properties prop = new Properties(); try (InputStream in = @@ -158,13 +165,13 @@ public Properties getProps() throws IOException { prop.setProperty("application.password", crowdApplicationPassword); prop.setProperty("crowd.server.url", crowdServerUrl); prop.setProperty("cookie.domain", cookieDomain); - return prop; + + return ClientPropertiesImpl.newInstanceFromProperties(prop); } @Bean - // @ConditionalOnProperty(name = "provision.auth.provider", havingValue = "crowd") + @ConditionalOnProperty(name = "provision.auth.provider", havingValue = "crowd") public CrowdLogoutHandler logoutHandler() throws IOException { - if (spafrontendEnabled) { OKResponseCrowdLogoutHandler handler = new OKResponseCrowdLogoutHandler(); handler.setHttpAuthenticator(httpAuthenticator()); @@ -179,7 +186,8 @@ public CrowdLogoutHandler logoutHandler() throws IOException { @Bean public SSOAuthProcessingFilter crowdSSOAuthenticationProcessingFilter() throws Exception { - SSOAuthProcessingFilter filter = new SSOAuthProcessingFilter(); + SSOAuthProcessingFilter filter = + new SSOAuthProcessingFilter(crowdHttpTokenHelper(), crowdClient(), getProps()); filter.setBasicAuthHandlerStrategy(ssoFilterBasicAuthHandlerStrategy()); filter.setHttpAuthenticator(httpAuthenticator()); filter.setAuthenticationManager(authenticationManager()); @@ -198,7 +206,6 @@ public SSOAuthProcessingFilterBasicAuthStrategy ssoFilterBasicAuthHandlerStrateg @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { - if (spafrontendEnabled) { return createAuthSuccessHandlerThatReturnsOK(); } else { @@ -266,7 +273,7 @@ public static void logSuccessAuthentication(Authentication authentication) { @Bean public AuthenticationFailureHandler authenticationFailureHandler() { UsernameStoringAuthenticationFailureHandler failureHandler = - new UsernameStoringAuthenticationFailureHandler(); + new UsernameStoringAuthenticationFailureHandler("username"); failureHandler.setDefaultFailureUrl("/login?error=true"); failureHandler.setUseForward(true); return failureHandler; @@ -277,46 +284,28 @@ public AuthenticationFailureHandler authenticationFailureHandler() { name = "provision.auth.provider", havingValue = "crowd", matchIfMissing = true) - public SecurityServerClient securityServerClient() throws IOException { - return new SecurityServerClientImpl( - SoapClientPropertiesImpl.newInstanceFromProperties(getProps())); + public CrowdClient crowdClient() throws IOException { + return new RestCrowdClientFactory().newInstance(getProps()); } @Bean - public BasicCache getCache() { - return new CacheImpl(getCacheManager()); + public CrowdAuthenticationAdapter crowdAuthenticationAdapter() throws IOException { + return new CrowdAuthenticationAdapter(crowdClient()); } @Bean - public CacheManager getCacheManager() { - return getEhCacheFactory().getObject(); + public CrowdHttpValidationFactorExtractor crowdHttpValidationFactorExtractor() { + return CrowdHttpValidationFactorExtractorImpl.getInstance(); } @Bean - public EhCacheManagerFactoryBean getEhCacheFactory() { - EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean(); - factoryBean.setConfigLocation(new ClassPathResource("crowd-ehcache.xml")); - return factoryBean; + public CrowdHttpTokenHelper crowdHttpTokenHelper() { + return CrowdHttpTokenHelperImpl.getInstance(crowdHttpValidationFactorExtractor()); } @Bean - public AuthenticationManager crowdAuthenticationManager() throws IOException { - return new CrowdAuthenticationManager(securityServerClient()); - } - - @Bean - public HttpAuthenticator httpAuthenticator() throws IOException { - return new HttpAuthenticatorImpl(crowdAuthenticationManager()); - } - - @Bean - public UserManager userManager() throws IOException { - return new CachingUserManager(securityServerClient(), getCache()); - } - - @Bean - public GroupManager groupManager() throws IOException { - return new CachingGroupManager(securityServerClient(), getCache()); + public CrowdHttpAuthenticator httpAuthenticator() throws IOException { + return new CrowdHttpAuthenticatorImpl(crowdClient(), getProps(), crowdHttpTokenHelper()); } @Override @@ -326,19 +315,99 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { @Bean public CrowdUserDetailsService crowdUserDetailsService() throws IOException { - CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl(); - cusd.setUserManager(userManager()); - cusd.setGroupMembershipManager( - new ProvAppSimpleCachingGroupMembershipManager( - securityServerClient(), userManager(), groupManager(), getCache(), true)); + CrowdUserDetailsServiceImpl cusd = + new CrowdUserDetailsServiceImpl() { + @Override + public CrowdUserDetails loadUserByUsername(String username) + throws UsernameNotFoundException, DataAccessException { + return updateCrowdUserDetails(super.loadUserByUsername(username)); + } + + @Override + public CrowdUserDetails loadUserByToken(String token) + throws CrowdSSOTokenInvalidException, DataAccessException { + return updateCrowdUserDetails(super.loadUserByToken(token)); + } + + /** + * Return the groups of user in LowerCase to avoid problems with the rest of + * authorization. + * + * @param crowdUserDetails CrowdUserDetails obtained with Authorities not processed + * @return CrowdUserDetails with Authorities in lowercase + */ + CrowdUserDetails updateCrowdUserDetails(CrowdUserDetails crowdUserDetails) { + ArrayList authorities = new ArrayList(); + crowdUserDetails + .getAuthorities() + .forEach( + authority -> + authorities.add( + new SimpleGrantedAuthority(authority.getAuthority().toLowerCase()))); + + return new CrowdUserDetails(crowdUserDetails.getRemotePrincipal(), authorities); + } + }; + cusd.setCrowdClient(crowdClient()); cusd.setAuthorityPrefix(""); + return cusd; } @Bean public RemoteCrowdAuthenticationProvider crowdAuthenticationProvider() throws IOException { return new RemoteCrowdAuthenticationProvider( - crowdAuthenticationManager(), httpAuthenticator(), crowdUserDetailsService()); + crowdClient(), httpAuthenticator(), crowdUserDetailsService()) { + + /** + * Added suppport for store password to connect with Atlassian. + * + * @param username username of the remote user. + * @param password password of the remote user. + * @param validationFactors validation factors from the remote user. + * @return + * @throws InactiveAccountException + * @throws ExpiredCredentialException + * @throws ApplicationPermissionException + * @throws InvalidAuthenticationException + * @throws OperationFailedException + * @throws ApplicationAccessDeniedException + */ + @Override + protected String authenticate( + String username, String password, List validationFactors) + throws InactiveAccountException, ExpiredCredentialException, + ApplicationPermissionException, InvalidAuthenticationException, + OperationFailedException, ApplicationAccessDeniedException { + UserAuthenticationContext userAuthenticationContext = + new UserAuthenticationContext( + username, + PasswordCredential.unencrypted(password), + validationFactors.toArray(new ValidationFactor[validationFactors.size()]), + null); + String token = authenticationManager.authenticateSSOUser(userAuthenticationContext); + + // Store credentials info in + userPassword.setToken(token); + userPassword.setUsername(userAuthenticationContext.getName()); + userPassword.setPassword(userAuthenticationContext.getCredential().getCredential()); + + return token; + } + + /** + * Added support for Basic Authentication using WebAuthenticationDetails + * + * @param authenticationToken AbstractAuthenticationToken + * @return support status + */ + public boolean supports(AbstractAuthenticationToken authenticationToken) { + // support all non-SSO authentication requests (for compatibility) + return (authenticationToken.getDetails() == null + || authenticationToken.getDetails() instanceof CrowdSSOAuthenticationDetails + || authenticationToken.getDetails() instanceof WebAuthenticationDetails); + } + }; } @Bean @@ -375,4 +444,9 @@ public BasicAuthenticationEntryPoint basicAuthEntryPoint() { entryPoint.setRealmName(idManagerRealm); return entryPoint; } + + @Bean + public String getCrowdServerUrl() { + return crowdServerUrl; + } } diff --git a/src/main/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManager.java b/src/main/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManager.java deleted file mode 100644 index b4c337d1..00000000 --- a/src/main/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManager.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.opendevstack.provision.authentication.crowd; - -import com.atlassian.crowd.exception.InvalidAuthenticationException; -import com.atlassian.crowd.exception.InvalidAuthorizationTokenException; -import com.atlassian.crowd.exception.UserNotFoundException; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import java.rmi.RemoteException; -import java.util.List; -import java.util.stream.Collectors; -import org.opendevstack.provision.authentication.SimpleCachingGroupMembershipManager; - -public class ProvAppSimpleCachingGroupMembershipManager - extends SimpleCachingGroupMembershipManager { - - private final boolean lowercaseGroupNames; - - public ProvAppSimpleCachingGroupMembershipManager( - SecurityServerClient securityServerClient, - UserManager userManager, - GroupManager groupManager, - BasicCache cache, - boolean lowercaseGroupNames) { - super(securityServerClient, userManager, groupManager, cache); - this.lowercaseGroupNames = lowercaseGroupNames; - } - - @Override - public List getMemberships(String user) - throws RemoteException, InvalidAuthorizationTokenException, UserNotFoundException, - InvalidAuthenticationException { - List memberships = super.getMemberships(user); - if (lowercaseGroupNames) { - List membershipsAsLowercase = - memberships.stream().map(group -> group.toLowerCase()).collect(Collectors.toList()); - return membershipsAsLowercase; - } else { - return memberships; - } - } -} diff --git a/src/main/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilter.java b/src/main/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilter.java index 288f14ce..d6c9fa0b 100644 --- a/src/main/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilter.java +++ b/src/main/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilter.java @@ -14,9 +14,16 @@ package org.opendevstack.provision.authentication.filter; -import com.atlassian.crowd.integration.http.HttpAuthenticator; +import com.atlassian.crowd.exception.ApplicationPermissionException; +import com.atlassian.crowd.exception.InvalidAuthenticationException; +import com.atlassian.crowd.exception.OperationFailedException; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticator; +import com.atlassian.crowd.integration.http.util.CrowdHttpTokenHelper; import com.atlassian.crowd.integration.springsecurity.CrowdSSOAuthenticationProcessingFilter; import com.atlassian.crowd.integration.springsecurity.CrowdSSOAuthenticationToken; +import com.atlassian.crowd.model.authentication.CookieConfiguration; +import com.atlassian.crowd.service.client.ClientProperties; +import com.atlassian.crowd.service.client.CrowdClient; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -31,10 +38,17 @@ public class SSOAuthProcessingFilter extends CrowdSSOAuthenticationProcessingFil private static final Logger logger = LoggerFactory.getLogger(SSOAuthProcessingFilter.class); - private HttpAuthenticator httpAuthenticator; + private CrowdHttpAuthenticator crowdHttpAuthenticator; private SSOAuthProcessingFilterBasicAuthStrategy basicAuthHandlerStrategy; + public SSOAuthProcessingFilter( + CrowdHttpTokenHelper tokenHelper, + CrowdClient crowdClient, + ClientProperties clientProperties) { + super(tokenHelper, crowdClient, clientProperties); + } + /** * Method to handle a successful authentication * @@ -52,7 +66,7 @@ protected void successfulAuthentication( FilterChain chain, Authentication authResult) throws IOException, ServletException { - storeTokenIfCrowd(request, response, authResult); + storeTokenIfCrowdMethodUsed(request, response, authResult); logger.debug("AuthResult {}", authResult.getCredentials().toString()); super.successfulAuthentication(request, response, chain, authResult); } @@ -62,8 +76,8 @@ protected void successfulAuthentication( * * @param httpAuthenticator */ - public void setHttpAuthenticator(HttpAuthenticator httpAuthenticator) { - this.httpAuthenticator = httpAuthenticator; + public void setHttpAuthenticator(CrowdHttpAuthenticator httpAuthenticator) { + this.crowdHttpAuthenticator = httpAuthenticator; super.setHttpAuthenticator(httpAuthenticator); } @@ -75,23 +89,21 @@ public void setHttpAuthenticator(HttpAuthenticator httpAuthenticator) { * @param response * @param authResult */ - boolean storeTokenIfCrowd( + boolean storeTokenIfCrowdMethodUsed( HttpServletRequest request, HttpServletResponse response, Authentication authResult) { if (authResult instanceof CrowdSSOAuthenticationToken && authResult.getCredentials() != null) { try { - httpAuthenticator.setPrincipalToken( - request, response, authResult.getCredentials().toString()); + super.storeTokenIfCrowd(request, response, authResult); return true; } catch (Exception e) { logger.error("Unable to set Crowd SSO token", e); - return false; } } return false; } - public HttpAuthenticator getAuthenticator() { - return httpAuthenticator; + public CrowdHttpAuthenticator getAuthenticator() { + return crowdHttpAuthenticator; } public void setBasicAuthHandlerStrategy( @@ -111,4 +123,14 @@ protected boolean requiresAuthentication( return super.requiresAuthentication(request, response); } + + @Override + protected CookieConfiguration getCookieConfiguration() + throws OperationFailedException, InvalidAuthenticationException, + ApplicationPermissionException { + return new CookieConfiguration( + clientProperties.getSSOCookieDomainName(), + super.getCookieConfiguration().isSecure(), + clientProperties.getCookieTokenKey()); + } } diff --git a/src/main/java/org/opendevstack/provision/authentication/oauth2/BasicAuthConfig.java b/src/main/java/org/opendevstack/provision/authentication/oauth2/BasicAuthConfig.java index 46cea611..1ebb4f4a 100644 --- a/src/main/java/org/opendevstack/provision/authentication/oauth2/BasicAuthConfig.java +++ b/src/main/java/org/opendevstack/provision/authentication/oauth2/BasicAuthConfig.java @@ -14,31 +14,27 @@ package org.opendevstack.provision.authentication.oauth2; -import com.atlassian.crowd.integration.http.HttpAuthenticator; -import com.atlassian.crowd.integration.http.HttpAuthenticatorImpl; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticator; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticatorImpl; +import com.atlassian.crowd.integration.http.util.CrowdHttpTokenHelperImpl; +import com.atlassian.crowd.integration.http.util.CrowdHttpValidationFactorExtractorImpl; +import com.atlassian.crowd.integration.rest.service.factory.RestCrowdClientFactory; import com.atlassian.crowd.integration.springsecurity.RemoteCrowdAuthenticationProvider; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsService; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl; -import com.atlassian.crowd.service.AuthenticationManager; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.*; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import com.atlassian.crowd.service.soap.client.SecurityServerClientImpl; -import com.atlassian.crowd.service.soap.client.SoapClientPropertiesImpl; +import com.atlassian.crowd.service.client.ClientProperties; +import com.atlassian.crowd.service.client.ClientPropertiesImpl; +import com.atlassian.crowd.service.client.CrowdClient; import java.io.IOException; import java.io.InputStream; import java.util.Properties; -import net.sf.ehcache.CacheManager; -import org.opendevstack.provision.authentication.crowd.ProvAppSimpleCachingGroupMembershipManager; +import org.opendevstack.provision.authentication.crowd.CrowdAuthenticationAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; @Configuration @@ -63,37 +59,33 @@ public class BasicAuthConfig { @Value("${crowd.cookie.domain}") String cookieDomain; - private SecurityServerClientImpl securityServerClient; - @Bean public RemoteCrowdAuthenticationProvider crowdAuthenticationProvider() throws IOException { logger.info( "Created RemoteCrowdAuthenticationProvider to enable REST API calls with Basic Auth beside OAuth2!"); return new RemoteCrowdAuthenticationProvider( - simpleCrowdAuthenticationManager(), httpAuthenticator(), crowdUserDetailsService()); + crowdClient(), httpAuthenticator(), crowdUserDetailsService()); } @Bean - public AuthenticationManager simpleCrowdAuthenticationManager() throws IOException { - return new SimpleAuthenticationManager(securityServerClient()); + public CrowdAuthenticationAdapter simpleCrowdAuthenticationManager() throws IOException { + return new CrowdAuthenticationAdapter(crowdClient()); } @Bean - public SecurityServerClient securityServerClient() throws IOException { - if (securityServerClient == null) { - securityServerClient = - new SecurityServerClientImpl( - SoapClientPropertiesImpl.newInstanceFromProperties(getProps())); - } - return securityServerClient; + public CrowdClient crowdClient() throws IOException { + return new RestCrowdClientFactory().newInstance(getProps()); } @Bean - public HttpAuthenticator httpAuthenticator() throws IOException { - return new HttpAuthenticatorImpl(simpleCrowdAuthenticationManager()); + public CrowdHttpAuthenticator httpAuthenticator() throws IOException { + return new CrowdHttpAuthenticatorImpl( + crowdClient(), + getProps(), + CrowdHttpTokenHelperImpl.getInstance(CrowdHttpValidationFactorExtractorImpl.getInstance())); } - private Properties getProps() throws IOException { + public ClientProperties getProps() throws IOException { Properties prop = new Properties(); try (InputStream in = @@ -104,47 +96,17 @@ private Properties getProps() throws IOException { prop.setProperty("application.password", crowdApplicationPassword); prop.setProperty("crowd.server.url", crowdServerUrl); prop.setProperty("cookie.domain", cookieDomain); - return prop; + + return ClientPropertiesImpl.newInstanceFromProperties(prop); } @Bean public CrowdUserDetailsService crowdUserDetailsService() throws IOException { CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl(); - cusd.setUserManager(userManager()); - cusd.setGroupMembershipManager( - new ProvAppSimpleCachingGroupMembershipManager( - securityServerClient(), userManager(), groupManager(), getCache(), true)); cusd.setAuthorityPrefix(""); return cusd; } - @Bean - public UserManager userManager() throws IOException { - return new CachingUserManager(securityServerClient(), getCache()); - } - - @Bean - public BasicCache getCache() { - return new CacheImpl(getCacheManager()); - } - - @Bean - public CacheManager getCacheManager() { - return getEhCacheFactory().getObject(); - } - - @Bean - public EhCacheManagerFactoryBean getEhCacheFactory() { - EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean(); - factoryBean.setConfigLocation(new ClassPathResource("crowd-ehcache.xml")); - return factoryBean; - } - - @Bean - public GroupManager groupManager() throws IOException { - return new CachingGroupManager(securityServerClient(), getCache()); - } - @Bean public BasicAuthenticationEntryPoint basicAuthEntryPoint() { BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); diff --git a/src/main/java/org/opendevstack/provision/authentication/oauth2/Oauth2AuthenticationManager.java b/src/main/java/org/opendevstack/provision/authentication/oauth2/Oauth2AuthenticationManager.java index 5b05c190..ebbb1cc6 100644 --- a/src/main/java/org/opendevstack/provision/authentication/oauth2/Oauth2AuthenticationManager.java +++ b/src/main/java/org/opendevstack/provision/authentication/oauth2/Oauth2AuthenticationManager.java @@ -1,5 +1,8 @@ package org.opendevstack.provision.authentication.oauth2; +import com.atlassian.crowd.exception.ApplicationPermissionException; +import com.atlassian.crowd.exception.InvalidAuthenticationException; +import com.atlassian.crowd.exception.OperationFailedException; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; import java.util.Optional; import org.opendevstack.provision.adapter.IODSAuthnzAdapter; @@ -59,6 +62,11 @@ public String getUserEmail() { .orElse(null); } + @Override + public void invalidate(String token) + throws InvalidAuthenticationException, OperationFailedException, + ApplicationPermissionException {} + @Override public void setUserPassword(String userPassword) { throw new UnsupportedOperationException("not supported in oauth2 or basic auth authentication"); diff --git a/src/main/java/org/opendevstack/provision/controller/DefaultController.java b/src/main/java/org/opendevstack/provision/controller/DefaultController.java index 73646a3b..9d22351e 100644 --- a/src/main/java/org/opendevstack/provision/controller/DefaultController.java +++ b/src/main/java/org/opendevstack/provision/controller/DefaultController.java @@ -249,12 +249,16 @@ public String logoutPage() { } private boolean isAuthenticated() { - if (isAuthProviderOauth2()) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (null != authentication) { + if (isAuthProviderOauth2()) { + return authentication.isAuthenticated(); + } - return authentication.isAuthenticated(); + return (authentication.isAuthenticated() && manager.getToken() != null); } - return (manager.getUserPassword() != null); + + return false; } @Autowired diff --git a/src/main/resources/crowd-ehcache.xml b/src/main/resources/crowd-ehcache.xml deleted file mode 100644 index fbd9e83d..00000000 --- a/src/main/resources/crowd-ehcache.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManagerTest.java b/src/test/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManagerTest.java deleted file mode 100644 index e64eb230..00000000 --- a/src/test/java/org/opendevstack/provision/authentication/SimpleCachingGroupMembershipManagerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.opendevstack.provision.authentication; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; - -import com.atlassian.crowd.integration.soap.SOAPPrincipal; -import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; -import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@DirtiesContext -@ActiveProfiles("crowd") -public class SimpleCachingGroupMembershipManagerTest { - - @Mock private SecurityServerClient securityServerClient; - - @Mock private UserManager usermanager; - - @Mock private GroupManager groupmanager; - - @Autowired private BasicCache basicCache; - - @Test - public void testSingleUserLookupWithGroupsAndCaching() throws Exception { - SOAPPrincipal user1 = new SOAPPrincipal(); - user1.setName("someuser1"); - - // known user with 2 roles - Mockito.when(usermanager.getUserFromToken(user1.getName())).thenReturn(user1); - - Mockito.when(securityServerClient.findGroupMemberships(user1.getName())) - .thenReturn(new String[] {"role2", "role1"}); - - CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl(); - cusd.setUserManager(usermanager); - cusd.setGroupMembershipManager( - new SimpleCachingGroupMembershipManager( - securityServerClient, usermanager, groupmanager, basicCache)); - cusd.setAuthorityPrefix(""); - - CrowdUserDetails details = cusd.loadUserByToken(user1.getName()); - assertNotNull(details); - - // 2 authorities come back - assertSame(2, details.getAuthorities().size()); - - Mockito.verify(securityServerClient).findGroupMemberships(user1.getName()); - details = cusd.loadUserByToken(user1.getName()); - assertNotNull(details); - - // 2 authorities come back - but no new external call - assertSame(2, details.getAuthorities().size()); - Mockito.verify(securityServerClient).findGroupMemberships(user1.getName()); - } - - @Test - public void testUserLookupNoGroups() throws Exception { - SOAPPrincipal user1 = new SOAPPrincipal(); - user1.setName("someuser10"); - - // known user with NULL roles .. just to ensure that a null - // does not blow up - Mockito.when(usermanager.getUserFromToken(user1.getName())).thenReturn(user1); - - // Mockito.when(securityServerClient.findGroupMemberships(user1.getName())). - // thenReturn(new String[] {}); - - CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl(); - cusd.setUserManager(usermanager); - cusd.setGroupMembershipManager( - new SimpleCachingGroupMembershipManager( - securityServerClient, usermanager, groupmanager, basicCache)); - cusd.setAuthorityPrefix(""); - - CrowdUserDetails details = cusd.loadUserByToken(user1.getName()); - assertNotNull(details); - - assertSame(0, details.getAuthorities().size()); - } - - @Test - public void testMultipleUserLookupWithGroups() throws Exception { - SOAPPrincipal user1 = new SOAPPrincipal(); - user1.setName("someuser"); - - // known user with 2 roles - Mockito.when(usermanager.getUserFromToken(user1.getName())).thenReturn(user1); - - Mockito.when(securityServerClient.findGroupMemberships(user1.getName())) - .thenReturn(new String[] {"role2", "role1"}); - - SOAPPrincipal user2 = new SOAPPrincipal(); - user2.setName("someuser2"); - - // known user with 2 roles - Mockito.when(usermanager.getUserFromToken(user2.getName())).thenReturn(user2); - - Mockito.when(securityServerClient.findGroupMemberships(user2.getName())) - .thenReturn(new String[] {"role3", "role4"}); - - CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl(); - cusd.setUserManager(usermanager); - cusd.setGroupMembershipManager( - new SimpleCachingGroupMembershipManager( - securityServerClient, usermanager, groupmanager, basicCache)); - cusd.setAuthorityPrefix(""); - - // load user 1 - 2 roles come back .. - CrowdUserDetails details = cusd.loadUserByToken(user1.getName()); - assertSame(2, details.getAuthorities().size()); - - assertEquals("[role2, role1]", details.getAuthorities().toString()); - - // load user 2 ... two other roles come back - details = cusd.loadUserByToken(user2.getName()); - assertNotNull(details); - assertEquals("[role3, role4]", details.getAuthorities().toString()); - } -} diff --git a/src/test/java/org/opendevstack/provision/authentication/TestAuthentication.java b/src/test/java/org/opendevstack/provision/authentication/TestAuthentication.java index f18cb093..96596f52 100644 --- a/src/test/java/org/opendevstack/provision/authentication/TestAuthentication.java +++ b/src/test/java/org/opendevstack/provision/authentication/TestAuthentication.java @@ -1,7 +1,7 @@ package org.opendevstack.provision.authentication; -import com.atlassian.crowd.integration.soap.SOAPPrincipal; import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetails; +import com.atlassian.crowd.model.user.UserTemplate; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -12,12 +12,13 @@ public class TestAuthentication implements Authentication { private String username = "clemens"; private String credentials; - private GrantedAuthority[] authorities; + private List authorities; private boolean authenticated; public TestAuthentication() {} - public TestAuthentication(String username, String credentials, GrantedAuthority[] authorities) { + public TestAuthentication( + String username, String credentials, List authorities) { this.username = username; this.credentials = credentials; this.authorities = authorities; @@ -29,9 +30,9 @@ public String getName() { } @Override - public Collection getAuthorities() { + public Collection getAuthorities() { if (authorities != null) { - return List.of(authorities); + return authorities; } else { List auths = new ArrayList<>(); auths.add(new TestAuthority()); @@ -52,13 +53,12 @@ public Object getDetails() { @Override public Object getPrincipal() { - SOAPPrincipal principal = new SOAPPrincipal(); - principal.setName(username); + UserTemplate principal = new UserTemplate(username); if (authorities != null) { return new CrowdUserDetails(principal, authorities); } else { - return new CrowdUserDetails(principal, getAuthorities().toArray(new GrantedAuthority[] {})); + return new CrowdUserDetails(principal, getAuthorities()); } } diff --git a/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapterTest.java b/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapterTest.java new file mode 100644 index 00000000..2b61540d --- /dev/null +++ b/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationAdapterTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * 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.opendevstack.provision.authentication.crowd; + +import static org.junit.jupiter.api.Assertions.*; + +import com.atlassian.crowd.embedded.api.PasswordCredential; +import com.atlassian.crowd.exception.ApplicationPermissionException; +import com.atlassian.crowd.exception.InvalidAuthenticationException; +import com.atlassian.crowd.exception.OperationFailedException; +import com.atlassian.crowd.model.authentication.Session; +import com.atlassian.crowd.model.authentication.UserAuthenticationContext; +import com.atlassian.crowd.model.authentication.ValidationFactor; +import com.atlassian.crowd.service.client.CrowdClient; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@DirtiesContext +@WithMockUser( + username = CrowdAuthenticationAdapterTest.TEST_USER, + roles = {"ADMIN"}) +@ActiveProfiles("crowd") +public class CrowdAuthenticationAdapterTest { + + private static final String TOKEN = "token"; + + /** This is for tests only */ + static final String TEST_USER = "test"; + + @SuppressWarnings("squid:S2068") + static final String TEST_CRED = "test"; + + @Autowired private CrowdAuthenticationAdapter manager; + + @Mock private CrowdClient crowdClient; + + @BeforeEach + public void setUp() throws Exception { + String token = TOKEN; + List factors = new ArrayList<>(); + Session testSession = createTestSession(); + Mockito.when(crowdClient.validateSSOAuthenticationAndGetSession("", factors)) + .thenReturn(testSession); + + Mockito.doThrow(new ApplicationPermissionException()) + .when(crowdClient) + .invalidateSSOToken("permission_exception"); + Mockito.doThrow(new OperationFailedException()) + .when(crowdClient) + .invalidateSSOToken("operation_failed"); + Mockito.doThrow(new InvalidAuthenticationException(token)) + .when(crowdClient) + .invalidateSSOToken("invalid_auth"); + manager.setCrowdClient(crowdClient); + } + + private Session createTestSession() { + return new Session() { + + Principal principal = + new Principal() { + @Override + public String getName() { + return TEST_USER; + } + }; + + @Override + public String getToken() { + return "test_token"; + } + + @Override + public Date getCreatedDate() { + return null; + } + + @Override + public Date getExpiryDate() { + return null; + } + + @Override + public Principal getUser() { + return principal; + } + }; + } + + @Test + public void setUserPassword() throws Exception { + String pass = TEST_CRED; + + manager.setUserPassword(pass); + + assertEquals(pass, manager.getUserPassword()); + } + + @Test + public void invalidateWithApplicationPermissionException() { + assertThrows( + ApplicationPermissionException.class, () -> manager.invalidate("permission_exception")); + } + + @Test + public void invalidateWithOperationFailedException() { + assertThrows(OperationFailedException.class, () -> manager.invalidate("operation_failed")); + } + + @Test + public void invalidateWithInvalidAuthenticationException() { + assertThrows(InvalidAuthenticationException.class, () -> manager.invalidate("invalid_auth")); + } + + @Test + public void getCrowdClient() { + assertNotNull(manager.getCrowdClient()); + assertTrue((manager.getCrowdClient() instanceof CrowdClient)); + } + + private UserAuthenticationContext getContext() { + UserAuthenticationContext context = new UserAuthenticationContext(); + context.setName(TEST_USER); + context.setValidationFactors(new ValidationFactor[0]); + PasswordCredential credential = new PasswordCredential(TEST_CRED); + context.setCredential(credential); + return context; + } +} diff --git a/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManagerTest.java b/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManagerTest.java deleted file mode 100644 index edbff141..00000000 --- a/src/test/java/org/opendevstack/provision/authentication/crowd/CrowdAuthenticationManagerTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * 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.opendevstack.provision.authentication.crowd; - -import static org.junit.jupiter.api.Assertions.*; - -import com.atlassian.crowd.embedded.api.PasswordCredential; -import com.atlassian.crowd.exception.InvalidAuthenticationException; -import com.atlassian.crowd.exception.InvalidAuthorizationTokenException; -import com.atlassian.crowd.model.authentication.UserAuthenticationContext; -import com.atlassian.crowd.model.authentication.ValidationFactor; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import com.atlassian.crowd.service.soap.client.SecurityServerClientImpl; -import com.atlassian.crowd.service.soap.client.SoapClientProperties; -import com.atlassian.crowd.service.soap.client.SoapClientPropertiesImpl; -import java.rmi.RemoteException; -import java.util.Properties; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@DirtiesContext -@WithMockUser( - username = CrowdAuthenticationManagerTest.USER, - roles = {"ADMIN"}) -@ActiveProfiles("crowd") -public class CrowdAuthenticationManagerTest { - - private static final String TOKEN = "token"; - - /** This is for tests only */ - static final String USER = "test"; - - @SuppressWarnings("squid:S2068") - static final String TEST_CRED = "test"; - - @Autowired private CrowdAuthenticationManager manager; - - @Mock private SecurityServerClient securityServerClient; - - @BeforeEach - public void setUp() throws Exception { - String token = TOKEN; - ValidationFactor[] factors = new ValidationFactor[0]; - Mockito.when(securityServerClient.isValidToken("", factors)).thenReturn(true); - - Mockito.doThrow(new RemoteException()).when(securityServerClient).invalidateToken("remote"); - Mockito.doThrow(new InvalidAuthorizationTokenException()) - .when(securityServerClient) - .invalidateToken("invalid_token"); - Mockito.doThrow(new InvalidAuthenticationException(token)) - .when(securityServerClient) - .invalidateToken("invalid_auth"); - manager.setSecurityServerClient(securityServerClient); - } - - @Test - public void setUserPassword() throws Exception { - String pass = TEST_CRED; - - manager.setUserPassword(pass); - - assertEquals(pass, manager.getUserPassword()); - } - - @Test - public void authenticateWithContext() throws Exception { - SecurityServerClient client = Mockito.mock(SecurityServerClientImpl.class); - Mockito.when(client.authenticatePrincipal(getContext())).thenReturn(null); - Mockito.when(client.getSoapClientProperties()).thenReturn(getProps()); - - manager.setSecurityServerClient(client); - - assertNull(manager.authenticate(getContext())); - } - - @Test - public void authenticateWithoutValidatingPassword() throws Exception { - SecurityServerClient client = Mockito.mock(SecurityServerClientImpl.class); - Mockito.when(client.authenticatePrincipal(getContext())).thenReturn(null); - Mockito.when(client.getSoapClientProperties()).thenReturn(getProps()); - - manager.setSecurityServerClient(client); - - assertNull(manager.authenticateWithoutValidatingPassword(getContext())); - } - - @Test - public void authenticateWithUsernameAndPassword() throws Exception { - SecurityServerClient client = Mockito.mock(SecurityServerClientImpl.class); - Mockito.when(client.authenticatePrincipalSimple(USER, TEST_CRED)).thenReturn("login"); - - manager.setSecurityServerClient(client); - - assertEquals("login", manager.authenticate(USER, TEST_CRED)); - } - - @Test - public void isAuthenticated() throws Exception { - assertTrue(manager.isAuthenticated("", new ValidationFactor[0])); - } - - @Test - public void invalidateWithRemoteException() { - assertThrows(RemoteException.class, () -> manager.invalidate("remote")); - } - - @Test - public void invalidateWithInvalidAuthorizationTokenException() { - assertThrows( - InvalidAuthorizationTokenException.class, () -> manager.invalidate("invalid_token")); - } - - @Test - public void invalidateWithInvalidAuthenticationException() { - assertThrows(InvalidAuthenticationException.class, () -> manager.invalidate("invalid_auth")); - } - - @Test - public void getSecurityServerClient() { - assertNotNull(manager.getSecurityServerClient()); - assertTrue((manager.getSecurityServerClient() instanceof SecurityServerClient)); - } - - private UserAuthenticationContext getContext() { - UserAuthenticationContext context = new UserAuthenticationContext(); - context.setName(USER); - context.setValidationFactors(new ValidationFactor[0]); - PasswordCredential credential = new PasswordCredential(USER); - context.setCredential(credential); - return context; - } - - private SoapClientProperties getProps() { - Properties plainProps = new Properties(); - plainProps.setProperty("application.name", USER); - SoapClientProperties props = SoapClientPropertiesImpl.newInstanceFromProperties(plainProps); - return props; - } -} diff --git a/src/test/java/org/opendevstack/provision/authentication/crowd/OKResponseCrowdLogoutHandlerTest.java b/src/test/java/org/opendevstack/provision/authentication/crowd/OKResponseCrowdLogoutHandlerTest.java index 4afa7046..702fe911 100644 --- a/src/test/java/org/opendevstack/provision/authentication/crowd/OKResponseCrowdLogoutHandlerTest.java +++ b/src/test/java/org/opendevstack/provision/authentication/crowd/OKResponseCrowdLogoutHandlerTest.java @@ -3,7 +3,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.atlassian.crowd.integration.http.HttpAuthenticator; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticator; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; @@ -17,7 +17,7 @@ public class OKResponseCrowdLogoutHandlerTest { @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; - @Mock private HttpAuthenticator authenticator; + @Mock private CrowdHttpAuthenticator authenticator; @Mock private Authentication authentication; @Test diff --git a/src/test/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManagerTest.java b/src/test/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManagerTest.java deleted file mode 100644 index fceab464..00000000 --- a/src/test/java/org/opendevstack/provision/authentication/crowd/ProvAppSimpleCachingGroupMembershipManagerTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * 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.opendevstack.provision.authentication.crowd; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - -import com.atlassian.crowd.exception.InvalidAuthenticationException; -import com.atlassian.crowd.exception.InvalidAuthorizationTokenException; -import com.atlassian.crowd.exception.UserNotFoundException; -import com.atlassian.crowd.service.GroupManager; -import com.atlassian.crowd.service.UserManager; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.soap.client.SecurityServerClient; -import java.rmi.RemoteException; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -@ExtendWith(SpringExtension.class) -public class ProvAppSimpleCachingGroupMembershipManagerTest { - - @MockBean private SecurityServerClient securityServerClient; - - @MockBean private UserManager userManager; - - @MockBean private GroupManager groupManager; - - @MockBean private BasicCache cache; - - @Test - public void getMemberships() - throws RemoteException, InvalidAuthorizationTokenException, UserNotFoundException, - InvalidAuthenticationException { - - String groupInUppercase = "GROUP"; - - when(cache.getAllMemberships(anyString())).thenReturn(List.of(groupInUppercase)); - - // case memberships are not converted to lowercase - ProvAppSimpleCachingGroupMembershipManager membershipManager = - new ProvAppSimpleCachingGroupMembershipManager( - securityServerClient, userManager, groupManager, cache, false); - - List memberships = membershipManager.getMemberships("user"); - assertTrue(memberships.contains(groupInUppercase)); - - // case memberships are converted to lowercase - membershipManager = - new ProvAppSimpleCachingGroupMembershipManager( - securityServerClient, userManager, groupManager, cache, true); - - memberships = membershipManager.getMemberships("user"); - assertTrue(memberships.contains(groupInUppercase.toLowerCase())); - } -} diff --git a/src/test/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilterTest.java b/src/test/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilterTest.java index 46bc8e05..ffce11ca 100644 --- a/src/test/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilterTest.java +++ b/src/test/java/org/opendevstack/provision/authentication/filter/SSOAuthProcessingFilterTest.java @@ -19,8 +19,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import com.atlassian.crowd.integration.http.HttpAuthenticator; +import com.atlassian.crowd.integration.http.CrowdHttpAuthenticator; +import com.atlassian.crowd.integration.http.util.CrowdHttpTokenHelper; import com.atlassian.crowd.integration.springsecurity.CrowdSSOAuthenticationToken; +import com.atlassian.crowd.service.client.ClientProperties; +import com.atlassian.crowd.service.client.CrowdClient; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; @@ -32,7 +35,7 @@ @ExtendWith(SpringExtension.class) public class SSOAuthProcessingFilterTest { - @MockBean private HttpAuthenticator httpAuthenticator; + @MockBean private CrowdHttpAuthenticator crowdHttpAuthenticator; @MockBean private HttpServletRequest request; @@ -40,20 +43,28 @@ public class SSOAuthProcessingFilterTest { @MockBean private SSOAuthProcessingFilterBasicAuthStrategy basicAuthStrategy; - private SSOAuthProcessingFilter filter = new SSOAuthProcessingFilter(); + @MockBean private CrowdClient crowdClient; + + @MockBean private CrowdHttpTokenHelper crowdHttpTokenHelper; + + @MockBean private ClientProperties clientProperties; + + private SSOAuthProcessingFilter filter; @BeforeEach public void setup() { + // TODO instantiate params + filter = new SSOAuthProcessingFilter(crowdHttpTokenHelper, crowdClient, clientProperties); filter.setBasicAuthHandlerStrategy(basicAuthStrategy); - filter.setHttpAuthenticator(httpAuthenticator); + filter.setHttpAuthenticator(crowdHttpAuthenticator); } @Test public void storeCrowdToken() { CrowdSSOAuthenticationToken token = new CrowdSSOAuthenticationToken("token"); - assertTrue(filter.storeTokenIfCrowd(request, response, token)); - assertFalse(filter.storeTokenIfCrowd(request, response, null)); + assertTrue(filter.storeTokenIfCrowdMethodUsed(request, response, token)); + assertFalse(filter.storeTokenIfCrowdMethodUsed(request, response, null)); } @Test diff --git a/src/test/java/org/opendevstack/provision/config/AuthSecurityTestConfig.java b/src/test/java/org/opendevstack/provision/config/AuthSecurityTestConfig.java index 918e4890..ca02abd3 100644 --- a/src/test/java/org/opendevstack/provision/config/AuthSecurityTestConfig.java +++ b/src/test/java/org/opendevstack/provision/config/AuthSecurityTestConfig.java @@ -16,17 +16,12 @@ import static java.util.Map.entry; import static org.mockito.Mockito.mock; -import com.atlassian.crowd.service.cache.BasicCache; -import com.atlassian.crowd.service.cache.CacheImpl; import java.util.Map; -import net.sf.ehcache.CacheManager; import org.opendevstack.provision.adapter.IODSAuthnzAdapter; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import org.springframework.core.io.ClassPathResource; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -63,26 +58,4 @@ public Map testUsersAndRoles() { public IODSAuthnzAdapter iodsAuthnzAdapter() { return mock(IODSAuthnzAdapter.class); } - - @Bean - @Primary - public BasicCache getCache() { - return new CacheImpl(getCacheManager()); - } - - @Bean - @Primary - public CacheManager getCacheManager() { - return getEhCacheFactory().getObject(); - } - - @Bean - @Primary - public EhCacheManagerFactoryBean getEhCacheFactory() { - EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean(); - factoryBean.setConfigLocation(new ClassPathResource("crowd-ehcache.xml")); - factoryBean.setShared(false); - factoryBean.setAcceptExisting(true); - return factoryBean; - } } diff --git a/src/test/java/org/opendevstack/provision/controller/DefaultControllerTest.java b/src/test/java/org/opendevstack/provision/controller/DefaultControllerTest.java index 0ab1760b..54e3f445 100644 --- a/src/test/java/org/opendevstack/provision/controller/DefaultControllerTest.java +++ b/src/test/java/org/opendevstack/provision/controller/DefaultControllerTest.java @@ -27,15 +27,13 @@ import org.opendevstack.provision.adapter.ICollaborationAdapter; import org.opendevstack.provision.adapter.IJobExecutionAdapter; import org.opendevstack.provision.adapter.ISCMAdapter; -import org.opendevstack.provision.authentication.TestAuthentication; -import org.opendevstack.provision.authentication.crowd.CrowdAuthenticationManager; +import org.opendevstack.provision.authentication.crowd.CrowdAuthenticationAdapter; import org.opendevstack.provision.model.AboutChangesData; import org.opendevstack.provision.services.StorageAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @@ -51,7 +49,7 @@ public class DefaultControllerTest { @MockBean private StorageAdapter storageAdapter; - @MockBean private CrowdAuthenticationManager crowdAuthenticationManager; + @MockBean private CrowdAuthenticationAdapter crowdAuthenticationAdapter; @Autowired private DefaultController defaultController; @@ -77,8 +75,7 @@ public void rootRedirect() throws Exception { @Test @WithMockUser(username = "test") public void homeWithoutAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn(null); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); + Mockito.when(crowdAuthenticationAdapter.getUserPassword()).thenReturn(null); mockMvc .perform(get("/home")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) @@ -88,8 +85,8 @@ public void homeWithoutAuth() throws Exception { @Test @WithMockUser(username = "test") public void homeWithAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn("logged_in"); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); + Mockito.when(crowdAuthenticationAdapter.getToken()).thenReturn("logged_in"); + defaultController.setCustomAuthenticationManager(crowdAuthenticationAdapter); mockMvc .perform(get("/home")) .andExpect(MockMvcResultMatchers.status().isOk()) @@ -99,11 +96,10 @@ public void homeWithAuth() throws Exception { @Test @WithMockUser(username = "test") public void provisionWithAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn("logged_in"); + Mockito.when(crowdAuthenticationAdapter.getToken()).thenReturn("logged_in"); Mockito.when(jobExecutionAdapter.getQuickstarterJobs()).thenReturn(new ArrayList<>()); defaultController.setJobExecutionAdapter(jobExecutionAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); - SecurityContextHolder.getContext().setAuthentication(new TestAuthentication()); + defaultController.setCustomAuthenticationManager(crowdAuthenticationAdapter); mockMvc .perform(get("/provision")) .andDo(MockMvcResultHandlers.print()) @@ -113,9 +109,8 @@ public void provisionWithAuth() throws Exception { @Test public void provisionWithoutAuth() throws Exception { Mockito.when(jobExecutionAdapter.getQuickstarterJobs()).thenReturn(new ArrayList<>()); - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn(null); + Mockito.when(crowdAuthenticationAdapter.getUserPassword()).thenReturn(null); defaultController.setJobExecutionAdapter(jobExecutionAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); mockMvc .perform(get("/provision")) .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) @@ -124,28 +119,27 @@ public void provisionWithoutAuth() throws Exception { @Test public void login() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn(null); + Mockito.when(crowdAuthenticationAdapter.getUserPassword()).thenReturn(null); Mockito.when(storageAdapter.listProjectHistory()).thenReturn(new HashMap<>()); defaultController.setStorageAdapter(storageAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); mockMvc.perform(get("/login")).andExpect(MockMvcResultMatchers.status().is2xxSuccessful()); } @Test public void history() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn(null); + Mockito.when(crowdAuthenticationAdapter.getUserPassword()).thenReturn(null); Mockito.when(storageAdapter.listProjectHistory()).thenReturn(new HashMap<>()); defaultController.setStorageAdapter(storageAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); mockMvc.perform(get("/history")).andExpect(MockMvcResultMatchers.status().is3xxRedirection()); } @Test + @WithMockUser(username = "test") public void historyWithAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn("logged_in"); + Mockito.when(crowdAuthenticationAdapter.getToken()).thenReturn("logged_in"); Mockito.when(storageAdapter.listProjectHistory()).thenReturn(new HashMap<>()); defaultController.setStorageAdapter(storageAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); + defaultController.setCustomAuthenticationManager(crowdAuthenticationAdapter); mockMvc.perform(get("/history")).andExpect(MockMvcResultMatchers.status().isOk()); } @@ -155,11 +149,12 @@ public void logoutPage() throws Exception { } @Test + @WithMockUser(username = "test") public void aboutWithAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn("logged_in"); + Mockito.when(crowdAuthenticationAdapter.getToken()).thenReturn("logged_in"); Mockito.when(storageAdapter.listAboutChangesData()).thenReturn(new AboutChangesData()); defaultController.setStorageAdapter(storageAdapter); - defaultController.setCustomAuthenticationManager(crowdAuthenticationManager); + defaultController.setCustomAuthenticationManager(crowdAuthenticationAdapter); // set the real thing defaultController.setSCMAdapter(realBitbucketAdapter); @@ -181,7 +176,7 @@ public void aboutWithAuth() throws Exception { @Test public void aboutWithoutAuth() throws Exception { - Mockito.when(crowdAuthenticationManager.getUserPassword()).thenReturn(null); + Mockito.when(crowdAuthenticationAdapter.getUserPassword()).thenReturn(null); mockMvc.perform(get("/about")).andExpect(MockMvcResultMatchers.status().is3xxRedirection()); } diff --git a/src/test/java/org/opendevstack/provision/controller/ProjectApiControllerTest.java b/src/test/java/org/opendevstack/provision/controller/ProjectApiControllerTest.java index 2604d57b..47183c30 100644 --- a/src/test/java/org/opendevstack/provision/controller/ProjectApiControllerTest.java +++ b/src/test/java/org/opendevstack/provision/controller/ProjectApiControllerTest.java @@ -121,10 +121,7 @@ public void setUp() { GrantedAuthority auth = () -> roleAdmin; SecurityContextHolder.getContext() .setAuthentication( - new TestAuthentication( - TEST_ADMIN_USERNAME, - TEST_VALID_CREDENTIAL, - List.of(auth).toArray(GrantedAuthority[]::new))); + new TestAuthentication(TEST_ADMIN_USERNAME, TEST_VALID_CREDENTIAL, List.of(auth))); initOpenProjectData(); diff --git a/src/test/java/org/opendevstack/provision/services/CrowdProjectIdentityMgmtAdapterTest.java b/src/test/java/org/opendevstack/provision/services/CrowdProjectIdentityMgmtAdapterTest.java index e1529bfb..066fa1f3 100644 --- a/src/test/java/org/opendevstack/provision/services/CrowdProjectIdentityMgmtAdapterTest.java +++ b/src/test/java/org/opendevstack/provision/services/CrowdProjectIdentityMgmtAdapterTest.java @@ -18,8 +18,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -import com.atlassian.crowd.integration.soap.SOAPGroup; -import com.atlassian.crowd.integration.soap.SOAPPrincipal; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -38,36 +36,37 @@ public class CrowdProjectIdentityMgmtAdapterTest { @Test public void testGroupExists() { - SOAPGroup group = new SOAPGroup("xxx", null); - when(manager.existsGroupWithName(eq(group.getName()))).thenReturn(true); + String group = "xxx"; - assertTrue(idMgr.groupExists(group.getName())); + when(manager.existsGroupWithName(eq(group))).thenReturn(true); + assertTrue(idMgr.groupExists(group)); - when(manager.existsGroupWithName(eq(group.getName()))).thenReturn(false); - assertFalse(idMgr.groupExists(group.getName())); + when(manager.existsGroupWithName(eq(group))).thenReturn(false); + assertFalse(idMgr.groupExists(group)); } @Test public void testUserExists() { - SOAPPrincipal principal = mockPrincipalExists("user", true); + String principal = "user"; - assertTrue(idMgr.userExists(principal.getName())); + when(manager.existPrincipalWithName(principal)).thenReturn(true); + assertTrue(idMgr.userExists(principal)); - when(manager.existPrincipalWithName(principal.getName())).thenReturn(false); - assertFalse(idMgr.userExists(principal.getName())); + when(manager.existPrincipalWithName(principal)).thenReturn(false); + assertFalse(idMgr.userExists(principal)); } @Test public void testCreateGroup() throws Exception { - SOAPGroup group = new SOAPGroup("xxx", null); + String group = "xxx"; - when(manager.addGroup(group.getName())).thenReturn(group.getName()); - String groupInternal = idMgr.createGroupInternal(group.getName()); - assertEquals(group.getName(), groupInternal); + when(manager.addGroup(group)).thenReturn(group); + String groupInternal = idMgr.createGroupInternal(group); + assertEquals(group, groupInternal); - assertEquals(group.getName(), idMgr.createAdminGroup(group.getName())); - assertEquals(group.getName(), idMgr.createUserGroup(group.getName())); - assertEquals(group.getName(), idMgr.createReadonlyGroup(group.getName())); + assertEquals(group, idMgr.createAdminGroup(group)); + assertEquals(group, idMgr.createUserGroup(group)); + assertEquals(group, idMgr.createReadonlyGroup(group)); } @Test @@ -80,22 +79,22 @@ public void testCreateGroupSOAPErr() { assertThrows( IdMgmtException.class, () -> { - SOAPGroup group = new SOAPGroup("xxx", null); - when(manager.addGroup(group.getName())).thenThrow(IdMgmtException.class); - idMgr.createGroupInternal(group.getName()); + String group = "xxx"; + when(manager.addGroup(group)).thenThrow(IdMgmtException.class); + idMgr.createGroupInternal(group); }); } @Test public void testValidateProject() throws Exception { - SOAPPrincipal principal = mockPrincipalExists("user", true); - SOAPGroup group = mockGroupExists("xxx", true); + String principal = mockPrincipalExists("user", true); + String group = mockGroupExists("xxx", true); OpenProjectData data = new OpenProjectData(); - data.setProjectAdminGroup(group.getName()); - data.setProjectUserGroup(group.getName()); - data.setProjectReadonlyGroup(group.getName()); - data.setProjectAdminUser(principal.getName()); + data.setProjectAdminGroup(group); + data.setProjectUserGroup(group); + data.setProjectReadonlyGroup(group); + data.setProjectAdminUser(principal); idMgr.validateIdSettingsOfProject(data); @@ -118,7 +117,7 @@ public void testValidateProject() throws Exception { assertTrue(testE.getMessage().contains(data.getProjectAdminGroup())); assertTrue(testE.getMessage().contains(data.getProjectReadonlyGroup())); - mockPrincipalExists(principal.getName(), false); + mockPrincipalExists(principal, false); testE = null; try { idMgr.validateIdSettingsOfProject(data); @@ -130,15 +129,13 @@ public void testValidateProject() throws Exception { assertTrue(testE.getMessage().contains(data.getProjectAdminUser())); } - public SOAPGroup mockGroupExists(String groupName, boolean existsGroup) { - SOAPGroup group = new SOAPGroup(groupName, null); - when(manager.existsGroupWithName(group.getName())).thenReturn(existsGroup); - return group; + public String mockGroupExists(String groupName, boolean existsGroup) { + when(manager.existsGroupWithName(groupName)).thenReturn(existsGroup); + return groupName; } - public SOAPPrincipal mockPrincipalExists(String user, boolean exists) { - SOAPPrincipal principal = new SOAPPrincipal(user); - when(manager.existPrincipalWithName(principal.getName())).thenReturn(exists); - return principal; + public String mockPrincipalExists(String user, boolean exists) { + when(manager.existPrincipalWithName(user)).thenReturn(exists); + return user; } } diff --git a/src/test/java/org/opendevstack/provision/services/MailAdapterTest.java b/src/test/java/org/opendevstack/provision/services/MailAdapterTest.java index 440dd704..c2ab2813 100644 --- a/src/test/java/org/opendevstack/provision/services/MailAdapterTest.java +++ b/src/test/java/org/opendevstack/provision/services/MailAdapterTest.java @@ -20,7 +20,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.provision.authentication.crowd.CrowdAuthenticationManager; +import org.opendevstack.provision.authentication.crowd.CrowdAuthenticationAdapter; import org.opendevstack.provision.model.OpenProjectData; import org.springframework.mail.javamail.JavaMailSender; @@ -29,14 +29,14 @@ public class MailAdapterTest { private MailAdapter mailAdapter; - @Mock private CrowdAuthenticationManager crowdAuthenticationManager; + @Mock private CrowdAuthenticationAdapter crowdAuthenticationAdapter; @Mock private JavaMailSender mailSender; @BeforeEach public void setUp() { mailAdapter = new MailAdapter(mailSender); - mailAdapter.manager = crowdAuthenticationManager; + mailAdapter.manager = crowdAuthenticationAdapter; } @Test diff --git a/src/test/java/org/opendevstack/provision/services/openshift/OpenshiftClientTest.java b/src/test/java/org/opendevstack/provision/services/openshift/OpenshiftClientTest.java index 33a8daa3..d5a9595a 100644 --- a/src/test/java/org/opendevstack/provision/services/openshift/OpenshiftClientTest.java +++ b/src/test/java/org/opendevstack/provision/services/openshift/OpenshiftClientTest.java @@ -15,7 +15,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.Assert.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import java.io.IOException;