diff --git a/README.adoc b/README.adoc index 8bc79af3..0d5885f1 100644 --- a/README.adoc +++ b/README.adoc @@ -52,6 +52,34 @@ security.oauth2.main.clientSecret=Value from Client Secret security.oauth2.pivotal-cla.tokenSecret=A Personal Access Token with public_repo scope ---- +== Configure properties for OAuth 2.0 Login + +This application uses the https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle/#jc-oauth2login[OAuth 2.0 Login] feature in Spring Security 5. + +Modify *application-local.properties* and append the following properties: + +.src/main/resources/application-local.properties +[source] +---- +security.oauth2.main.clientId=Value from Client ID +security.oauth2.main.clientSecret=Value from Client Secret +security.oauth2.pivotal-cla.tokenSecret=A Personal Access Token with public_repo scope + +spring.security.oauth2.client.registration.cla-user.provider=github +spring.security.oauth2.client.registration.cla-user.clientId=${security.oauth2.main.clientId} +spring.security.oauth2.client.registration.cla-user.clientSecret=${security.oauth2.main.clientSecret} +spring.security.oauth2.client.registration.cla-user.redirectUriTemplate={baseUrl}/login/oauth2/github +spring.security.oauth2.client.registration.cla-user.scope=user:email +spring.security.oauth2.client.registration.cla-user.clientName=GitHub + +spring.security.oauth2.client.registration.cla-admin.provider=github +spring.security.oauth2.client.registration.cla-admin.clientId=${security.oauth2.main.clientId} +spring.security.oauth2.client.registration.cla-admin.clientSecret=${security.oauth2.main.clientSecret} +spring.security.oauth2.client.registration.cla-admin.redirectUriTemplate={baseUrl}/login/oauth2/github +spring.security.oauth2.client.registration.cla-admin.scope=user:email, repo:status, admin:repo_hook, admin:org_hook, read:org +spring.security.oauth2.client.registration.cla-admin.clientName=GitHub Admin +---- + == Setup ngrok If you are needing to test receiving GitHub events, you will probably want to setup https://ngrok.com/#download[ngrok]. diff --git a/build.gradle b/build.gradle index 2a6e0492..8ecc2a3b 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ dependencies { compile('org.springframework.cloud:spring-cloud-spring-service-connector') compile('org.springframework.cloud:spring-cloud-cloudfoundry-connector') compile('org.springframework.session:spring-session-data-redis') + compile('org.springframework.security:spring-security-oauth2-client') compile('org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5') compile('org.webjars:webjars-locator-core') compile('org.webjars:bootstrap:3.3.6') diff --git a/src/main/java/io/pivotal/cla/config/SecurityConfig.java b/src/main/java/io/pivotal/cla/config/SecurityConfig.java index 13b879b4..0e6a9d05 100644 --- a/src/main/java/io/pivotal/cla/config/SecurityConfig.java +++ b/src/main/java/io/pivotal/cla/config/SecurityConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -15,14 +15,12 @@ */ package io.pivotal.cla.config; -import java.io.IOException; -import java.util.LinkedHashMap; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import io.pivotal.cla.data.User; +import io.pivotal.cla.mvc.util.UrlBuilder; +import io.pivotal.cla.security.GitHubAuthenticationSuccessHandler; +import io.pivotal.cla.service.github.GitHubOAuth2UserService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.InsufficientAuthenticationException; @@ -32,9 +30,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; @@ -43,8 +45,11 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.cors.CorsUtils; -import io.pivotal.cla.data.User; -import io.pivotal.cla.security.GitHubAuthenticationEntryPoint; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.LinkedHashMap; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -77,7 +82,27 @@ protected void configure(HttpSecurity http) throws Exception { .anyRequest().authenticated() .and() .logout() - .logoutSuccessUrl("/?logout"); + .logoutSuccessUrl("/?logout") + .and() + .oauth2Login() + .redirectionEndpoint() + .baseUri("/login/oauth2/*") + .and() + .userInfoEndpoint() + .userService(this.gitHubOAuth2UserService()) + .and() + .successHandler(this.gitHubAuthenticationSuccessHandler()); + + } + + @Bean + public OAuth2UserService gitHubOAuth2UserService() { + return new GitHubOAuth2UserService(); + } + + @Bean + public AuthenticationSuccessHandler gitHubAuthenticationSuccessHandler() { + return new GitHubAuthenticationSuccessHandler(); } static class AdminRequestedAccessDeniedHandler implements AccessDeniedHandler { @@ -117,16 +142,17 @@ private User getUser(Authentication authentication) { } } - private AuthenticationEntryPoint entryPoint() { LinkedHashMap entryPoints = new LinkedHashMap<>(); entryPoints.put(new AntPathRequestMatcher("/github/hooks/**"), new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); - entryPoints.put(new AntPathRequestMatcher("/admin/**"), new GitHubAuthenticationEntryPoint(oauthConfig.getMain(), "user:email,repo:status,admin:repo_hook,admin:org_hook,read:org")); + entryPoints.put(new AntPathRequestMatcher("/admin/**"), + (request, response, authException) -> response.sendRedirect(UrlBuilder.fromRequest(request).authorizationUrl("cla-admin"))); + BasicAuthenticationEntryPoint basicEntryPoint = new BasicAuthenticationEntryPoint(); basicEntryPoint.setRealmName("Pivotal CLA"); entryPoints.put(new AntPathRequestMatcher("/manage/**"), basicEntryPoint); DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(entryPoints); - entryPoint.setDefaultEntryPoint(new GitHubAuthenticationEntryPoint(oauthConfig.getMain(), "user:email")); + entryPoint.setDefaultEntryPoint((request, response, authException) -> response.sendRedirect(UrlBuilder.fromRequest(request).authorizationUrl("cla-user"))); return entryPoint; } -} +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/data/User.java b/src/main/java/io/pivotal/cla/data/User.java index d08583e2..7102cd61 100644 --- a/src/main/java/io/pivotal/cla/data/User.java +++ b/src/main/java/io/pivotal/cla/data/User.java @@ -40,7 +40,8 @@ public class User implements Serializable { @Column(name = "github_login") String gitHubLogin; - String name; + @Column(name = "name") + String fullName; String accessToken; diff --git a/src/main/java/io/pivotal/cla/mvc/CclaController.java b/src/main/java/io/pivotal/cla/mvc/CclaController.java index 55bf2c70..929dc352 100644 --- a/src/main/java/io/pivotal/cla/mvc/CclaController.java +++ b/src/main/java/io/pivotal/cla/mvc/CclaController.java @@ -70,7 +70,7 @@ public String claForm(@AuthenticationPrincipal User user, SignCorporateClaForm s } signCorporateClaForm.setSigned(signed != null); - signCorporateClaForm.setName(user.getName()); + signCorporateClaForm.setName(user.getFullName()); signCorporateClaForm.setClaId(cla.getId()); signCorporateClaForm.setRepositoryId(repositoryId); signCorporateClaForm.setPullRequestId(pullRequestId); diff --git a/src/main/java/io/pivotal/cla/mvc/IclaController.java b/src/main/java/io/pivotal/cla/mvc/IclaController.java index 14446109..dde17ced 100644 --- a/src/main/java/io/pivotal/cla/mvc/IclaController.java +++ b/src/main/java/io/pivotal/cla/mvc/IclaController.java @@ -59,7 +59,7 @@ public String claForm(@AuthenticationPrincipal User user, @ModelAttribute SignCl cla = cla.getSupersedingCla(); } signClaForm.setSigned(signed != null); - signClaForm.setName(user.getName()); + signClaForm.setName(user.getFullName()); signClaForm.setClaId(cla.getId()); model.put("cla", cla); diff --git a/src/main/java/io/pivotal/cla/mvc/security/OAuthController.java b/src/main/java/io/pivotal/cla/mvc/security/OAuthController.java deleted file mode 100644 index 7ed0167a..00000000 --- a/src/main/java/io/pivotal/cla/mvc/security/OAuthController.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.mvc.security; - -import io.pivotal.cla.data.IndividualSignature; -import io.pivotal.cla.data.User; -import io.pivotal.cla.data.repository.CorporateSignatureRepository; -import io.pivotal.cla.data.repository.IndividualSignatureRepository; -import io.pivotal.cla.data.repository.UserRepository; -import io.pivotal.cla.mvc.support.ImportedSignaturesSessionAttr; -import io.pivotal.cla.mvc.util.UrlBuilder; -import io.pivotal.cla.security.GitHubAuthenticationEntryPoint; -import io.pivotal.cla.security.Login; -import io.pivotal.cla.service.github.CurrentUserRequest; -import io.pivotal.cla.service.github.GitHubApi; -import io.pivotal.cla.service.github.OAuthAccessTokenParams; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -@Controller -public class OAuthController { - AuthenticationSuccessHandler success = new SavedRequestAwareAuthenticationSuccessHandler(); - - @Autowired - GitHubApi gitHub; - @Autowired - IndividualSignatureRepository individual; - @Autowired - CorporateSignatureRepository corporate; - - @Autowired - UserRepository users; - - @GetMapping("/login/oauth2/github") - public void oauth(ImportedSignaturesSessionAttr importedSignaturesAttr, HttpServletRequest request, HttpServletResponse response, @RequestParam String code, - @RequestParam String state) throws Exception { - String actualState = (String) request.getSession().getAttribute("state"); - if(actualState == null || !actualState.equals(state)) { - throw new InvalidSecretState(); - } - - boolean admin = GitHubAuthenticationEntryPoint.isAdmin(state); - - OAuthAccessTokenParams params = new OAuthAccessTokenParams(); - params.setCallbackUrl(UrlBuilder.fromRequest(request).callbackUrl()); - params.setCode(code); - params.setState(actualState); - - CurrentUserRequest userRequest = new CurrentUserRequest(); - userRequest.setOauthParams(params); - userRequest.setRequestAdminAccess(admin); - - User user = gitHub.getCurrentUser(userRequest); - - User existingUser = users.findOne(user.getGitHubLogin()); - boolean isNewUser = existingUser == null; - - users.save(user); - - Authentication authentication = Login.loginAs(user); - - if(isNewUser) { - List individualSignatures = individual.findSignaturesFor(new PageRequest(0, 1), user); - boolean signed = !individualSignatures.isEmpty(); - if(!signed) { - List organizations = gitHub.getOrganizations(user.getGitHubLogin()); - signed = !corporate.findSignatures(new PageRequest(0, 1), organizations, user.getEmails()).isEmpty(); - } - - if(signed) { - importedSignaturesAttr.setValue(true); - } - } - - success.onAuthenticationSuccess(request, response, authentication); - } - - @ExceptionHandler - @ResponseStatus(code= HttpStatus.BAD_REQUEST) - @ResponseBody - public String handleInvalidSecretState(InvalidSecretState error) { - return "Invalid Secret State"; - } - - @SuppressWarnings("serial") - static class InvalidSecretState extends RuntimeException {} -} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/mvc/util/UrlBuilder.java b/src/main/java/io/pivotal/cla/mvc/util/UrlBuilder.java index ce8bd75a..f030ef22 100644 --- a/src/main/java/io/pivotal/cla/mvc/util/UrlBuilder.java +++ b/src/main/java/io/pivotal/cla/mvc/util/UrlBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -43,8 +43,8 @@ private UrlBuilder(HttpServletRequest request) { this.request = request; } - public String callbackUrl() { - return path("/login/oauth2/github").build(); + public String authorizationUrl(String registrationId) { + return path("/oauth2/authorization/" + registrationId).build(); } public UrlBuilder param(String name, String value) { diff --git a/src/main/java/io/pivotal/cla/security/GitHubAuthenticationEntryPoint.java b/src/main/java/io/pivotal/cla/security/GitHubAuthenticationEntryPoint.java deleted file mode 100644 index c5f58999..00000000 --- a/src/main/java/io/pivotal/cla/security/GitHubAuthenticationEntryPoint.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.security; - -import java.io.IOException; -import java.util.UUID; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.web.util.UriComponentsBuilder; - -import io.pivotal.cla.config.OAuthClientCredentials; -import io.pivotal.cla.mvc.util.UrlBuilder; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class GitHubAuthenticationEntryPoint implements AuthenticationEntryPoint { - OAuthClientCredentials config; - String scope; - - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { - - String secretState = statePrefix() + UUID.randomUUID().toString(); - - request.getSession().setAttribute("state", secretState); - - String callbackUrl = UrlBuilder.fromRequest(request).callbackUrl(); - String redirectUrl = UriComponentsBuilder.fromHttpUrl("https://github.com/login/oauth/authorize") - .queryParam("client_id", config.getClientId()) - .queryParam("redirect_uri", callbackUrl) - .queryParam("state", secretState) - .queryParam("scope", scope) - .build() - .toUriString(); - - response.sendRedirect(redirectUrl); - } - - public static boolean isAdmin(String secretState) { - return secretState != null && secretState.startsWith("ADMIN"); - } - - private String statePrefix() { - return scope.contains("repo:status") ? "ADMIN" : "USER"; - } -} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/security/GitHubAuthenticationSuccessHandler.java b/src/main/java/io/pivotal/cla/security/GitHubAuthenticationSuccessHandler.java new file mode 100644 index 00000000..89f1c1e3 --- /dev/null +++ b/src/main/java/io/pivotal/cla/security/GitHubAuthenticationSuccessHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2018 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 io.pivotal.cla.security; + +import io.pivotal.cla.data.IndividualSignature; +import io.pivotal.cla.data.repository.CorporateSignatureRepository; +import io.pivotal.cla.data.repository.IndividualSignatureRepository; +import io.pivotal.cla.mvc.support.ImportedSignaturesSessionAttr; +import io.pivotal.cla.service.github.GitHubApi; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * @author Joe Grandja + */ +public class GitHubAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private final AuthenticationSuccessHandler savedRequestSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler(); + + @Autowired + private GitHubApi gitHubApi; + + @Autowired + private IndividualSignatureRepository individual; + + @Autowired + private CorporateSignatureRepository corporate; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + GitHubOAuth2User user = (GitHubOAuth2User) authentication.getPrincipal(); + if (user.isNewUser()) { + List individualSignatures = this.individual.findSignaturesFor(PageRequest.of(0, 1), user); + boolean signed = !individualSignatures.isEmpty(); + if (!signed) { + List organizations = this.gitHubApi.getOrganizations(user.getGitHubLogin()); + signed = !this.corporate.findSignatures(PageRequest.of(0, 1), organizations, user.getEmails()).isEmpty(); + } + if (signed) { + new ImportedSignaturesSessionAttr(new ServletWebRequest(request)).setValue(true); + } + } + + this.savedRequestSuccessHandler.onAuthenticationSuccess(request, response, authentication); + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/security/GitHubOAuth2User.java b/src/main/java/io/pivotal/cla/security/GitHubOAuth2User.java new file mode 100644 index 00000000..af59567e --- /dev/null +++ b/src/main/java/io/pivotal/cla/security/GitHubOAuth2User.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2018 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 io.pivotal.cla.security; + +import io.pivotal.cla.data.User; +import lombok.EqualsAndHashCode; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +/** + * @author Joe Grandja + */ +@EqualsAndHashCode(callSuper=true, exclude = {"newUser"}) +public class GitHubOAuth2User extends User implements OAuth2User { + private final Collection authorities; + private final Map attributes; + private final boolean newUser; + + public GitHubOAuth2User(Collection authorities, Map attributes) { + this(authorities, attributes, false); + } + + public GitHubOAuth2User(Collection authorities, Map attributes, boolean newUser) { + Assert.notEmpty(authorities, "authorities cannot be empty"); + Assert.notEmpty(attributes, "attributes cannot be empty"); + this.authorities = Collections.unmodifiableSet(new LinkedHashSet<>(authorities)); + this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); + this.newUser = newUser; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public Map getAttributes() { + return this.attributes; + } + + @Override + public String getName() { + return this.getGitHubLogin(); + } + + boolean isNewUser() { + return this.newUser; + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/security/Login.java b/src/main/java/io/pivotal/cla/security/Login.java deleted file mode 100644 index 6bb50c24..00000000 --- a/src/main/java/io/pivotal/cla/security/Login.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.security; - -import java.util.Collection; -import java.util.List; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import io.pivotal.cla.data.User; - -public class Login { - - public static Authentication loginAs(User user) { - SecurityContext context = SecurityContextHolder.createEmptyContext(); - UserAuthentication authentication = new UserAuthentication(user); - context.setAuthentication(authentication); - SecurityContextHolder.setContext(context); - return authentication; - } - - static class UserAuthentication implements Authentication { - private static final List ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"); - - private static final List CLA_AUTHOR_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN", "ROLE_CLA_AUTHOR", "ACTUATOR"); - - private static final List USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER"); - - private static final long serialVersionUID = 4717809728702726728L; - - final User user; - - public UserAuthentication(User user) { - this.user = user; - } - - @Override - public String getName() { - return String.valueOf(user.getGitHubLogin()); - } - - @Override - public Collection getAuthorities() { - if(user.isAdmin()) { - return user.isClaAuthor() ? CLA_AUTHOR_ROLES : ADMIN_ROLES; - } - return USER_ROLES; - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public Object getDetails() { - return null; - } - - @Override - public Object getPrincipal() { - return user; - } - - @Override - public boolean isAuthenticated() { - return true; - } - - @Override - public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/io/pivotal/cla/service/github/AccessTokenRequest.java b/src/main/java/io/pivotal/cla/service/github/AccessTokenRequest.java deleted file mode 100644 index 7c192072..00000000 --- a/src/main/java/io/pivotal/cla/service/github/AccessTokenRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.service.github; - -import io.pivotal.cla.config.OAuthClientCredentials; -import lombok.Data; - -/** - * @author Rob Winch - * - */ -@Data -public class AccessTokenRequest { - - OAuthClientCredentials credentials; - - OAuthAccessTokenParams oauthParams; - -} diff --git a/src/main/java/io/pivotal/cla/service/github/CurrentUserRequest.java b/src/main/java/io/pivotal/cla/service/github/CurrentUserRequest.java deleted file mode 100644 index cd2097da..00000000 --- a/src/main/java/io/pivotal/cla/service/github/CurrentUserRequest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.service.github; - -import lombok.Data; - -/** - * @author Rob Winch - * - */ -@Data -public class CurrentUserRequest { - OAuthAccessTokenParams oauthParams; - - boolean requestAdminAccess; - -} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/service/github/GitHubApi.java b/src/main/java/io/pivotal/cla/service/github/GitHubApi.java index 61b12e15..9eee6de5 100644 --- a/src/main/java/io/pivotal/cla/service/github/GitHubApi.java +++ b/src/main/java/io/pivotal/cla/service/github/GitHubApi.java @@ -19,7 +19,6 @@ import java.util.Optional; import java.util.Set; -import io.pivotal.cla.data.User; import io.pivotal.cla.egit.github.core.PullRequestId; import io.pivotal.cla.service.MigratePullRequestStatusRequest; import org.eclipse.egit.github.core.PullRequest; @@ -48,8 +47,6 @@ public interface GitHubApi { List createPullRequestHooks(CreatePullRequestHookRequest request); - User getCurrentUser(CurrentUserRequest request); - Set getVerifiedEmails(String accessToken); List getOrganizations(String username); diff --git a/src/main/java/io/pivotal/cla/service/github/GitHubOAuth2UserService.java b/src/main/java/io/pivotal/cla/service/github/GitHubOAuth2UserService.java new file mode 100644 index 00000000..f6be1a21 --- /dev/null +++ b/src/main/java/io/pivotal/cla/service/github/GitHubOAuth2UserService.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2018 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 io.pivotal.cla.service.github; + +import io.pivotal.cla.config.ClaOAuthConfig; +import io.pivotal.cla.data.User; +import io.pivotal.cla.data.repository.UserRepository; +import io.pivotal.cla.security.GitHubOAuth2User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * @author Joe Grandja + */ +public class GitHubOAuth2UserService implements OAuth2UserService { + private final OAuth2UserService defaultUserService = new DefaultOAuth2UserService(); + + private final RestTemplate restTemplate = new RestTemplate(); + + @Autowired + private GitHubApi gitHubApi; + + @Autowired + private ClaOAuthConfig oauthConfig; + + @Autowired + private UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oauth2User = this.defaultUserService.loadUser(userRequest); + + Map userAttributes = oauth2User.getAttributes(); + String login = userAttributes.containsKey("login") ? userAttributes.get("login").toString() : null; + String name = userAttributes.containsKey("name") ? userAttributes.get("name").toString() : null; + String avatarUrl = userAttributes.containsKey("avatar_url") ? userAttributes.get("avatar_url").toString() : null; + + OAuth2AccessToken accessToken = userRequest.getAccessToken(); + + Set verifiedEmails = this.gitHubApi.getVerifiedEmails(accessToken.getTokenValue()); + + boolean hasAdminEmail = verifiedEmails.stream().anyMatch(e -> e.endsWith(MylynGitHubApi.ADMIN_MAIL_SUFFIX)); + boolean hasAdminAccess = accessToken.getScopes().stream().anyMatch(s -> s.equals("repo:status")); + boolean isAdmin = hasAdminEmail && hasAdminAccess; + boolean isClaAuthor = isAdmin && this.isAuthor(login, accessToken.getTokenValue()); + + User user = new User(); + user.setFullName(name); + user.setAccessToken(accessToken.getTokenValue()); + user.setAvatarUrl(avatarUrl); + user.setEmails(new TreeSet<>(verifiedEmails)); + user.setGitHubLogin(login); + user.setAdminAccessRequested(hasAdminAccess); + user.setAdmin(isAdmin); + user.setClaAuthor(isClaAuthor); + + User existingUser = this.userRepository.findOne(user.getGitHubLogin()); + boolean isNewUser = existingUser == null; + this.userRepository.save(user); + + List authorities; + if (isClaAuthor) { + authorities = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN", "ROLE_CLA_AUTHOR", "ACTUATOR"); + } else if (isAdmin) { + authorities = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"); + } else { + authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); + } + + GitHubOAuth2User gitHubUser = new GitHubOAuth2User(authorities, userAttributes, isNewUser); + gitHubUser.setFullName(user.getFullName()); + gitHubUser.setAccessToken(user.getAccessToken()); + gitHubUser.setAvatarUrl(user.getAvatarUrl()); + gitHubUser.setEmails(user.getEmails()); + gitHubUser.setGitHubLogin(user.getGitHubLogin()); + gitHubUser.setAdminAccessRequested(user.isAdminAccessRequested()); + gitHubUser.setAdmin(user.isAdmin()); + gitHubUser.setClaAuthor(user.isClaAuthor()); + + return gitHubUser; + } + + private boolean isAuthor(String username, String accessToken) { + try { + ResponseEntity entity = this.restTemplate.getForEntity( + this.oauthConfig.getGitHubApiBaseUrl() + "/teams/{id}/memberships/{username}?access_token={token}", + String.class, "2006839", username, accessToken); + return entity.getStatusCode().value() == 200; + } catch (HttpClientErrorException ex) { + if (ex.getStatusCode() == HttpStatus.NOT_FOUND) { + return false; + } + throw ex; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/pivotal/cla/service/github/MylynGitHubApi.java b/src/main/java/io/pivotal/cla/service/github/MylynGitHubApi.java index 47f20c08..14c6d742 100644 --- a/src/main/java/io/pivotal/cla/service/github/MylynGitHubApi.java +++ b/src/main/java/io/pivotal/cla/service/github/MylynGitHubApi.java @@ -36,18 +36,13 @@ import org.eclipse.egit.github.core.service.RepositoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import com.fasterxml.jackson.annotation.JsonProperty; - import io.pivotal.cla.config.ClaOAuthConfig; -import io.pivotal.cla.config.OAuthClientCredentials; -import io.pivotal.cla.data.User; import io.pivotal.cla.egit.github.core.ContextCommitStatus; import io.pivotal.cla.egit.github.core.Email; import io.pivotal.cla.egit.github.core.EventsRepositoryHook; @@ -59,7 +54,6 @@ import io.pivotal.cla.egit.github.core.service.WithPermissionsRepositoryService; import io.pivotal.cla.mvc.util.UrlBuilder; import io.pivotal.cla.service.MigratePullRequestStatusRequest; -import lombok.Data; import lombok.SneakyThrows; /** @@ -69,7 +63,6 @@ @Component public class MylynGitHubApi implements GitHubApi { - private static final String AUTHORIZE_URI = "login/oauth/access_token"; public final static String CONTRIBUTING_FILE = "CONTRIBUTING"; public final static String ADMIN_MAIL_SUFFIX = "@pivotal.io"; public final static Pattern PULL_REQUEST_CALLBACK_PATTERN = Pattern.compile(".*" + UrlBuilder.pullRequestHookCallbackPath("") + "([a-zA-Z0-9\\-\\s\\%\\+]*)(\\?.*)?"); @@ -85,14 +78,12 @@ public class MylynGitHubApi implements GitHubApi { public final static String FREQUENTLY_ASKED_QUESTIONS = "frequently asked questions"; final ClaOAuthConfig oauthConfig; - final String authorizeUrl; final RestTemplate rest = new RestTemplate(); @Autowired public MylynGitHubApi(ClaOAuthConfig oauthConfig) { super(); this.oauthConfig = oauthConfig; - this.authorizeUrl = oauthConfig.getGitHubBaseUrl() + AUTHORIZE_URI; } @Override @@ -380,53 +371,12 @@ private List getComments(PullRequestId pullRequestId, PullRequest return service.getComments(pullRequestId.getRepositoryId(), pullRequestId.getId()); } - public User getCurrentUser(CurrentUserRequest request) { - AccessTokenRequest tokenRequest = new AccessTokenRequest(); - tokenRequest.setCredentials(oauthConfig.getMain()); - tokenRequest.setOauthParams(request.getOauthParams()); - String accessToken = getToken(tokenRequest); - - Set verifiedEmails = getVerifiedEmails(accessToken); - org.eclipse.egit.github.core.User currentGitHubUser = getCurrentGitHubUser(accessToken); - - User user = new User(); - user.setName(currentGitHubUser.getName()); - user.setAccessToken(accessToken); - user.setAvatarUrl(currentGitHubUser.getAvatarUrl()); - user.setEmails(new TreeSet<>(verifiedEmails)); - user.setGitHubLogin(currentGitHubUser.getLogin()); - user.setAdminAccessRequested(request.isRequestAdminAccess()); - boolean isAdmin = request.isRequestAdminAccess() && hasAdminEmail(user); - user.setAdmin(isAdmin); - if(isAdmin) { - boolean isClaAuthor = isAuthor(user.getGitHubLogin(), accessToken); - user.setClaAuthor(isClaAuthor); - } - return user; - } - public Set getVerifiedEmails(String accessToken) { EmailService emailService = EmailService.forOAuth(accessToken, oauthConfig); return emailService.getEmails().stream().filter(e -> e.isVerified()) .map(Email::getEmail).collect(Collectors.toSet()); } - private String getToken(AccessTokenRequest request) { - OAuthAccessTokenParams oauthParams = request.getOauthParams(); - Map params = new HashMap(); - OAuthClientCredentials credentials = request.getCredentials(); - - params.put("client_id", credentials.getClientId()); - params.put("client_secret", credentials.getClientSecret()); - params.put("code", oauthParams.getCode()); - params.put("state", oauthParams.getState()); - params.put("redirect_url", oauthParams.getCallbackUrl()); - - ResponseEntity token = rest.postForEntity(this.authorizeUrl, params, AccessTokenResponse.class); - - return token.getBody().getAccessToken(); - } - @SneakyThrows private org.eclipse.egit.github.core.User getCurrentGitHubUser(String accessToken) { GitHubClient client = createClient(accessToken); @@ -443,22 +393,6 @@ public List getOrganizations(String username) { return organizations.stream().map(o -> o.getLogin()).sorted(String.CASE_INSENSITIVE_ORDER).collect(Collectors.toList()); } - private boolean hasAdminEmail(User user) { - return user.getEmails().stream().anyMatch(e -> e.endsWith(ADMIN_MAIL_SUFFIX)); - } - - private boolean isAuthor(String username, String accessToken) { - try { - ResponseEntity entity = rest.getForEntity(oauthConfig.getGitHubApiBaseUrl() + "/teams/{id}/memberships/{username}?access_token={token}", String.class, "2006839", username, accessToken); - return entity.getStatusCode().value() == 200; - } catch(HttpClientErrorException e) { - if(e.getStatusCode() == HttpStatus.NOT_FOUND) { - return false; - } - throw e; - } - } - @Override @SneakyThrows public List createPullRequestHooks(CreatePullRequestHookRequest request) @@ -642,14 +576,4 @@ private EventsRepositoryHook createHook(String url, String secret) { hook.setConfig(config); return hook; } - - @Data - private static class AccessTokenResponse { - @JsonProperty("access_token") - String accessToken; - @JsonProperty("token_type") - String tokenType; - String scope; - - } } diff --git a/src/main/java/io/pivotal/cla/service/github/OAuthAccessTokenParams.java b/src/main/java/io/pivotal/cla/service/github/OAuthAccessTokenParams.java deleted file mode 100644 index 21022fb8..00000000 --- a/src/main/java/io/pivotal/cla/service/github/OAuthAccessTokenParams.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2016 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 io.pivotal.cla.service.github; - -import lombok.Data; - -/** - * @author Rob Winch - * - */ -@Data -public class OAuthAccessTokenParams { - - String state; - - String callbackUrl; - - String code; - - -} diff --git a/src/test/java/io/pivotal/cla/MocksConfig.java b/src/test/java/io/pivotal/cla/MocksConfig.java index 3a2487cf..051e4b32 100644 --- a/src/test/java/io/pivotal/cla/MocksConfig.java +++ b/src/test/java/io/pivotal/cla/MocksConfig.java @@ -30,7 +30,7 @@ static class GitHubOAuthAuthorizeController { @RequestMapping("/login/oauth/authorize") public String response(@RequestParam String state) { - return "redirect:https://localhost/login/oauth2/github?code=abc&state=" + state; + return "redirect:http://localhost/login/oauth2/github?code=abc&state=" + state; } } } diff --git a/src/test/java/io/pivotal/cla/security/UserAuthentication.java b/src/test/java/io/pivotal/cla/security/UserAuthentication.java new file mode 100644 index 00000000..6a3ed5f9 --- /dev/null +++ b/src/test/java/io/pivotal/cla/security/UserAuthentication.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2018 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 io.pivotal.cla.security; + +import io.pivotal.cla.data.User; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; + +import java.util.Collection; +import java.util.List; + +class UserAuthentication implements Authentication { + private static final List ADMIN_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"); + + private static final List CLA_AUTHOR_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN", "ROLE_CLA_AUTHOR", "ACTUATOR"); + + private static final List USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER"); + + private static final long serialVersionUID = 4717809728702726728L; + + final User user; + + public UserAuthentication(User user) { + this.user = user; + } + + @Override + public String getName() { + return String.valueOf(user.getGitHubLogin()); + } + + @Override + public Collection getAuthorities() { + if(user.isAdmin()) { + return user.isClaAuthor() ? CLA_AUTHOR_ROLES : ADMIN_ROLES; + } + return USER_ROLES; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return user; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/src/test/java/io/pivotal/cla/security/WithAdminUserFactory.java b/src/test/java/io/pivotal/cla/security/WithAdminUserFactory.java index 4c70409c..4afea0ce 100644 --- a/src/test/java/io/pivotal/cla/security/WithAdminUserFactory.java +++ b/src/test/java/io/pivotal/cla/security/WithAdminUserFactory.java @@ -15,14 +15,15 @@ */ package io.pivotal.cla.security; -import java.util.Collections; - +import io.pivotal.cla.data.User; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import io.pivotal.cla.data.User; -import io.pivotal.cla.security.Login.UserAuthentication; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class WithAdminUserFactory implements WithSecurityContextFactory { @@ -36,11 +37,17 @@ public SecurityContext createSecurityContext(WithAdminUser user) { } public static User create() { - User user = new User(); + String login = "rwinch"; + String avatarUrl = "https://avatars.githubusercontent.com/u/362503?v=3"; + Map attributes = new HashMap<>(); + attributes.put("id", "1234"); + attributes.put("login", login); + attributes.put("avatar_url", avatarUrl); + GitHubOAuth2User user = new GitHubOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN"), attributes); + user.setGitHubLogin(login); user.setAccessToken("mocked_access_token"); - user.setAvatarUrl("https://avatars.githubusercontent.com/u/362503?v=3"); + user.setAvatarUrl(avatarUrl); user.setEmails(Collections.singleton("rob@pivotal.io")); - user.setGitHubLogin("rwinch"); user.setAdmin(true); return user; } diff --git a/src/test/java/io/pivotal/cla/security/WithClaAuthorUserFactory.java b/src/test/java/io/pivotal/cla/security/WithClaAuthorUserFactory.java index 013fd213..7147a280 100644 --- a/src/test/java/io/pivotal/cla/security/WithClaAuthorUserFactory.java +++ b/src/test/java/io/pivotal/cla/security/WithClaAuthorUserFactory.java @@ -15,12 +15,15 @@ */ package io.pivotal.cla.security; +import io.pivotal.cla.data.User; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import io.pivotal.cla.data.User; -import io.pivotal.cla.security.Login.UserAuthentication; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class WithClaAuthorUserFactory implements WithSecurityContextFactory { @@ -34,8 +37,20 @@ public SecurityContext createSecurityContext(WithClaAuthorUser user) { } public static User create() { - User user = WithAdminUserFactory.create(); + String login = "rwinch"; + String avatarUrl = "https://avatars.githubusercontent.com/u/362503?v=3"; + Map attributes = new HashMap<>(); + attributes.put("id", "1234"); + attributes.put("login", login); + attributes.put("avatar_url", avatarUrl); + GitHubOAuth2User user = new GitHubOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN", "ROLE_CLA_AUTHOR"), attributes); + user.setGitHubLogin(login); + user.setAccessToken("mocked_access_token"); + user.setAvatarUrl(avatarUrl); + user.setEmails(Collections.singleton("rob@pivotal.io")); + user.setAdmin(true); user.setClaAuthor(true); return user; + } } \ No newline at end of file diff --git a/src/test/java/io/pivotal/cla/security/WithSigningUserFactory.java b/src/test/java/io/pivotal/cla/security/WithSigningUserFactory.java index e43ca096..59034d65 100644 --- a/src/test/java/io/pivotal/cla/security/WithSigningUserFactory.java +++ b/src/test/java/io/pivotal/cla/security/WithSigningUserFactory.java @@ -15,14 +15,15 @@ */ package io.pivotal.cla.security; -import java.util.Collections; - +import io.pivotal.cla.data.User; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import io.pivotal.cla.data.User; -import io.pivotal.cla.security.Login.UserAuthentication; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class WithSigningUserFactory implements WithSecurityContextFactory { @@ -37,12 +38,17 @@ public SecurityContext createSecurityContext(WithSigningUser user) { } public static User create() { - User user = new User(); + String login = "robwinch"; + String avatarUrl = "https://avatars.githubusercontent.com/u/362503?v=3"; + Map attributes = new HashMap<>(); + attributes.put("id", "1234"); + attributes.put("login", login); + attributes.put("avatar_url", avatarUrl); + GitHubOAuth2User user = new GitHubOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"), attributes); + user.setGitHubLogin(login); user.setAccessToken("mocked_access_token"); - user.setAvatarUrl("https://avatars.githubusercontent.com/u/362503?v=3"); + user.setAvatarUrl(avatarUrl); user.setEmails(Collections.singleton("rob@gmail.com")); - user.setGitHubLogin("robwinch"); return user; } - } diff --git a/src/test/java/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests.java b/src/test/java/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests.java new file mode 100644 index 00000000..68352fee --- /dev/null +++ b/src/test/java/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests.java @@ -0,0 +1,214 @@ +/* + * Copyright 2002-2018 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 io.pivotal.cla.service.github; + +import io.pivotal.cla.config.ClaOAuthConfig; +import io.pivotal.cla.data.User; +import io.pivotal.cla.data.repository.UserRepository; +import io.pivotal.cla.security.GitHubOAuth2User; +import io.pivotal.cla.security.WithAdminUserFactory; +import io.pivotal.cla.security.WithSigningUserFactory; +import okhttp3.mockwebserver.EnqueueRequests; +import okhttp3.mockwebserver.EnqueueResourcesMockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = WebEnvironment.MOCK) +public class GitHubOAuth2UserServiceTests { + private final String[] userAuthorities = {"ROLE_USER"}; + private final String[] adminAuthorities = {"ROLE_USER", "ROLE_ADMIN"}; + private final String[] authorAuthorities = {"ROLE_USER", "ROLE_ADMIN", "ROLE_CLA_AUTHOR", "ACTUATOR"}; + + @Rule + public final EnqueueResourcesMockWebServer server = new EnqueueResourcesMockWebServer(); + + @MockBean + private GitHubApi gitHubApi; + + @MockBean + private ClaOAuthConfig oauthConfig; + + @MockBean + private UserRepository userRepository; + + @MockBean + private ClientRegistrationRepository clientRegistrationRepository; + + @Autowired + private GitHubOAuth2UserService userService; + + private ClientRegistration claUserRegistration = ClientRegistration.withRegistrationId("cla-user") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri("https://github.com/login/oauth/access_token") + .userInfoUri(this.server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub") + .build(); + + private ClientRegistration claAdminRegistration = ClientRegistration.withRegistrationId("cla-admin") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email", "repo:status", "admin:repo_hook", "admin:org_hook", "read:org") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri("https://github.com/login/oauth/access_token") + .userInfoUri(this.server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub Admin") + .build(); + + @Before + public void setup() { + when(this.oauthConfig.getGitHubApiBaseUrl()).thenReturn(this.server.getServer().url("/").toString()); + } + + @Test + @EnqueueRequests("getUserInfo") + public void loadUserSigning() throws Exception { + OAuth2AccessToken accessToken = this.createAccessToken(this.claUserRegistration.getScopes().toArray(new String[0])); + OAuth2UserRequest userRequest = new OAuth2UserRequest(this.claUserRegistration, accessToken); + this.loadVerifyUser(userRequest); + } + + @Test + @EnqueueRequests("getUserInfo") + public void loadUserAdminRequestedButNotAdmin() throws Exception { + OAuth2AccessToken accessToken = this.createAccessToken("repo:status"); // Admin scope - triggers adminAccessRequested + OAuth2UserRequest userRequest = new OAuth2UserRequest(this.claUserRegistration, accessToken); + this.loadVerifyUser(userRequest); + } + + @Test + @EnqueueRequests({"getUserInfo", "isAuthor"}) + public void loadUserAdminAndClaAuthor() throws Exception { + OAuth2AccessToken accessToken = this.createAccessToken(this.claAdminRegistration.getScopes().toArray(new String[0])); + OAuth2UserRequest userRequest = new OAuth2UserRequest(this.claAdminRegistration, accessToken); + this.loadVerifyAdminUser(userRequest, true); + } + + @Test + @EnqueueRequests({"getUserInfo", "notAuthor"}) + public void loadUserAdminAndNotClaAuthor() throws Exception { + OAuth2AccessToken accessToken = this.createAccessToken(this.claAdminRegistration.getScopes().toArray(new String[0])); + OAuth2UserRequest userRequest = new OAuth2UserRequest(this.claAdminRegistration, accessToken); + this.loadVerifyAdminUser(userRequest, false); + } + + private void loadVerifyUser(OAuth2UserRequest userRequest) throws Exception { + User regularUser = WithSigningUserFactory.create(); + when(this.gitHubApi.getVerifiedEmails(any())).thenReturn(regularUser.getEmails()); + + GitHubOAuth2User user = (GitHubOAuth2User) this.userService.loadUser(userRequest); + + OAuth2AccessToken accessToken = userRequest.getAccessToken(); + boolean isAdminAccessRequested = accessToken.getScopes().stream().anyMatch(s -> s.equals("repo:status")); + + assertThat(user.isAdminAccessRequested()).isEqualTo(isAdminAccessRequested); + assertThat(user.isAdmin()).isFalse(); + assertThat(user.isClaAuthor()).isFalse(); + assertThat(user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) + .containsOnly(this.userAuthorities); + assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); + assertThat(user.getFullName()).isEqualTo("Rob Winch"); + assertThat(user.getEmails()).containsOnly("rob@gmail.com"); + assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); + assertThat(user.getAccessToken()).isEqualTo(accessToken.getTokenValue()); + + RecordedRequest request = this.server.getServer().takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/user"); + assertThat(request.getHeader("Authorization")).isEqualTo("Bearer " + user.getAccessToken()); + } + + private void loadVerifyAdminUser(OAuth2UserRequest userRequest, boolean isAuthor) throws Exception { + User adminUser = WithAdminUserFactory.create(); + when(this.gitHubApi.getVerifiedEmails(any())).thenReturn(adminUser.getEmails()); + + GitHubOAuth2User user = (GitHubOAuth2User) this.userService.loadUser(userRequest); + + OAuth2AccessToken accessToken = userRequest.getAccessToken(); + + assertThat(user.isAdminAccessRequested()).isTrue(); + assertThat(user.isAdmin()).isTrue(); + assertThat(user.isClaAuthor()).isEqualTo(isAuthor); + if (isAuthor) { + assertThat(user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) + .containsOnly(this.authorAuthorities); + } else { + assertThat(user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) + .containsOnly(this.adminAuthorities); + } + assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); + assertThat(user.getFullName()).isEqualTo("Rob Winch"); + assertThat(user.getEmails()).containsOnly("rob@pivotal.io"); + assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); + assertThat(user.getAccessToken()).isEqualTo(accessToken.getTokenValue()); + + RecordedRequest request = this.server.getServer().takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/user"); + assertThat(request.getHeader("Authorization")).isEqualTo("Bearer " + user.getAccessToken()); + + request = this.server.getServer().takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/teams/2006839/memberships/rwinch?access_token=access-token-123"); + } + + private OAuth2AccessToken createAccessToken(String... scope) { + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plusSeconds(300); + Set scopes = Arrays.stream(scope).collect(Collectors.toSet()); + return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-123", issuedAt, expiresAt, scopes); + } +} \ No newline at end of file diff --git a/src/test/java/io/pivotal/cla/service/github/MylynGitHubApiITests.java b/src/test/java/io/pivotal/cla/service/github/MylynGitHubApiITests.java index 53917eae..225dbc6b 100644 --- a/src/test/java/io/pivotal/cla/service/github/MylynGitHubApiITests.java +++ b/src/test/java/io/pivotal/cla/service/github/MylynGitHubApiITests.java @@ -32,7 +32,6 @@ import io.pivotal.cla.config.ClaOAuthConfig; import io.pivotal.cla.config.OAuthClientCredentials; -import io.pivotal.cla.data.User; import okhttp3.mockwebserver.EnqueueRequests; import okhttp3.mockwebserver.EnqueueResourcesMockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -85,184 +84,6 @@ public void findRepositoryNames() throws Exception { assertThat(request.getHeader("Authorization")).isEqualTo("token " + accessToken); } - @Test - @EnqueueRequests({"getAccessToken", - "getEmailsNotPivotal", - "getUserRwinch"}) - public void getCurrentUserAdminRequestedButNotAdmin() throws Exception { - OAuthAccessTokenParams oauthParams = new OAuthAccessTokenParams(); - oauthParams.setCallbackUrl("https://example.com/oauth/callback"); - oauthParams.setCode("code-123"); - oauthParams.setState("state-456"); - - CurrentUserRequest userRequest = new CurrentUserRequest(); - userRequest.setOauthParams(oauthParams); - userRequest.setRequestAdminAccess(true); - - User user = service.getCurrentUser(userRequest); - - assertThat(user.getAccessToken()).isEqualTo("access-token-123"); - assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); - assertThat(user.getEmails()).containsOnly("rob@example.com"); - assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); - assertThat(user.getName()).isEqualTo("Rob Winch"); - assertThat(user.isAdminAccessRequested()).isTrue(); - assertThat(user.isAdmin()).isFalse(); - - RecordedRequest request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()) - .isEqualTo("/login/oauth/access_token"); - assertThat(request.getBody().readUtf8()) - .isEqualTo("{\"code\":\"code-123\",\"client_secret\":\"client-secret\",\"state\":\"state-456\",\"client_id\":\"client-id\",\"redirect_url\":\"https://example.com/oauth/callback\"}"); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user/emails?per_page=100&page=1"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - } - - @Test - @EnqueueRequests({"getAccessToken", - "getEmailsPivotal", - "getUserRwinch", - "getTeamMembers"}) - public void getCurrentUserAdminAndClaAuthor() throws Exception { - OAuthAccessTokenParams oauthParams = new OAuthAccessTokenParams(); - oauthParams.setCallbackUrl("https://example.com/oauth/callback"); - oauthParams.setCode("code-123"); - oauthParams.setState("state-456"); - CurrentUserRequest userRequest = new CurrentUserRequest(); - userRequest.setOauthParams(oauthParams); - userRequest.setRequestAdminAccess(true); - - User user = service.getCurrentUser(userRequest); - - assertThat(user.getAccessToken()).isEqualTo("access-token-123"); - assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); - assertThat(user.getEmails()).containsOnly("rob@example.com","rob@pivotal.io"); - assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); - assertThat(user.getName()).isEqualTo("Rob Winch"); - assertThat(user.isAdminAccessRequested()).isTrue(); - assertThat(user.isAdmin()).isTrue(); - assertThat(user.isClaAuthor()).isTrue(); - - RecordedRequest request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()) - .isEqualTo("/login/oauth/access_token"); - assertThat(request.getBody().readUtf8()) - .isEqualTo("{\"code\":\"code-123\",\"client_secret\":\"client-secret\",\"state\":\"state-456\",\"client_id\":\"client-id\",\"redirect_url\":\"https://example.com/oauth/callback\"}"); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user/emails?per_page=100&page=1"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - } - - @Test - @EnqueueRequests({"getAccessToken", - "getEmailsPivotal", - "getUserRwinch", - "getTeamMembersNotFound"}) - public void getCurrentUserAdminAndNotClaAuthor() throws Exception { - OAuthAccessTokenParams oauthParams = new OAuthAccessTokenParams(); - oauthParams.setCallbackUrl("https://example.com/oauth/callback"); - oauthParams.setCode("code-123"); - oauthParams.setState("state-456"); - CurrentUserRequest userRequest = new CurrentUserRequest(); - userRequest.setOauthParams(oauthParams); - userRequest.setRequestAdminAccess(true); - - User user = service.getCurrentUser(userRequest); - - assertThat(user.getAccessToken()).isEqualTo("access-token-123"); - assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); - assertThat(user.getEmails()).containsOnly("rob@example.com","rob@pivotal.io"); - assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); - assertThat(user.getName()).isEqualTo("Rob Winch"); - assertThat(user.isAdminAccessRequested()).isTrue(); - assertThat(user.isAdmin()).isTrue(); - assertThat(user.isClaAuthor()).isFalse(); - - RecordedRequest request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()) - .isEqualTo("/login/oauth/access_token"); - assertThat(request.getBody().readUtf8()) - .isEqualTo("{\"code\":\"code-123\",\"client_secret\":\"client-secret\",\"state\":\"state-456\",\"client_id\":\"client-id\",\"redirect_url\":\"https://example.com/oauth/callback\"}"); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user/emails?per_page=100&page=1"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - } - - - @Test - @EnqueueRequests({"getAccessToken", - "getEmailsNotPivotal", - "getUserRwinch"}) - public void getCurrentUserSigning() throws Exception { - OAuthAccessTokenParams oauthParams = new OAuthAccessTokenParams(); - oauthParams.setCallbackUrl("https://example.com/oauth/callback"); - oauthParams.setCode("code-123"); - oauthParams.setState("state-456"); - CurrentUserRequest userRequest = new CurrentUserRequest(); - userRequest.setOauthParams(oauthParams); - userRequest.setRequestAdminAccess(false); - - User user = service.getCurrentUser(userRequest); - - assertThat(user.getAccessToken()).isEqualTo("access-token-123"); - assertThat(user.getAvatarUrl()).isEqualTo("https://avatars.githubusercontent.com/u/362503?v=3"); - assertThat(user.getEmails()).containsOnly("rob@example.com"); - assertThat(user.getGitHubLogin()).isEqualTo("rwinch"); - assertThat(user.getName()).isEqualTo("Rob Winch"); - assertThat(user.isAdminAccessRequested()).isFalse(); - assertThat(user.isAdmin()).isFalse(); - - RecordedRequest request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()) - .isEqualTo("/login/oauth/access_token"); - assertThat(request.getBody().readUtf8()) - .isEqualTo("{\"code\":\"code-123\",\"client_secret\":\"client-secret\",\"state\":\"state-456\",\"client_id\":\"client-id\",\"redirect_url\":\"https://example.com/oauth/callback\"}"); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user/emails?per_page=100&page=1"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - - request = server.getServer().takeRequest(); - assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()) - .isEqualTo("/api/v3/user"); - assertThat(request.getHeader("Authorization")).isEqualTo("token " + user.getAccessToken()); - } - @Test @EnqueueRequests("getOrganizations") public void getOrganizations() throws Exception { diff --git a/src/test/java/io/pivotal/cla/webdriver/AccessDeniedTests.java b/src/test/java/io/pivotal/cla/webdriver/AccessDeniedTests.java index 60749321..85a2f3c9 100644 --- a/src/test/java/io/pivotal/cla/webdriver/AccessDeniedTests.java +++ b/src/test/java/io/pivotal/cla/webdriver/AccessDeniedTests.java @@ -15,27 +15,20 @@ */ package io.pivotal.cla.webdriver; -import static org.mockito.Matchers.any; +import io.pivotal.cla.security.WithSigningUser; +import io.pivotal.cla.webdriver.pages.admin.AdminLinkClaPage; +import org.junit.Test; + import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.junit.Test; - -import io.pivotal.cla.data.User; -import io.pivotal.cla.security.WithSigningUser; -import io.pivotal.cla.security.WithSigningUserFactory; -import io.pivotal.cla.service.github.CurrentUserRequest; -import io.pivotal.cla.webdriver.pages.admin.AdminLinkClaPage; - public class AccessDeniedTests extends BaseWebDriverTests { @Test @WithSigningUser(requestedAdmin = true) public void adminForbiddenForUserRequestedAdmin() throws Exception { - User user = WithSigningUserFactory.create(); when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(user); String url = AdminLinkClaPage.url(); @@ -46,9 +39,7 @@ public void adminForbiddenForUserRequestedAdmin() throws Exception { @Test @WithSigningUser public void adminRedirectForUserNotRequestedAdmin() throws Exception { - User user = WithSigningUserFactory.create(); when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(user); String url = AdminLinkClaPage.url(); diff --git a/src/test/java/io/pivotal/cla/webdriver/AuthenticationTests.java b/src/test/java/io/pivotal/cla/webdriver/AuthenticationTests.java index 7b730e65..d85a950e 100644 --- a/src/test/java/io/pivotal/cla/webdriver/AuthenticationTests.java +++ b/src/test/java/io/pivotal/cla/webdriver/AuthenticationTests.java @@ -15,45 +15,80 @@ */ package io.pivotal.cla.webdriver; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anySet; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.test.context.TestSecurityContextHolder; -import org.springframework.util.MultiValueMap; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - import io.pivotal.cla.config.ClaOAuthConfig; import io.pivotal.cla.data.User; import io.pivotal.cla.security.WithAdminUserFactory; import io.pivotal.cla.security.WithSigningUser; import io.pivotal.cla.security.WithSigningUserFactory; -import io.pivotal.cla.service.github.CurrentUserRequest; -import io.pivotal.cla.service.github.OAuthAccessTokenParams; import io.pivotal.cla.webdriver.pages.SignClaPage; import io.pivotal.cla.webdriver.pages.admin.AdminLinkClaPage; +import okhttp3.mockwebserver.EnqueueRequests; +import okhttp3.mockwebserver.EnqueueResourcesMockWebServer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.test.context.TestSecurityContextHolder; + +import java.util.Arrays; +import java.util.Collections; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class AuthenticationTests extends BaseWebDriverTests { - @Autowired + @Rule + public EnqueueResourcesMockWebServer server = new EnqueueResourcesMockWebServer(); + + @SpyBean ClaOAuthConfig config; + private ClientRegistration claAdminRegistration = ClientRegistration.withRegistrationId("cla-admin") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email", "repo:status", "admin:repo_hook", "admin:org_hook", "read:org") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri(server.getServer().url("/login/oauth/access_token").toString()) + .userInfoUri(server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub Admin") + .build(); + + private ClientRegistration claUserRegistration = ClientRegistration.withRegistrationId("cla-user") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri(server.getServer().url("/login/oauth/access_token").toString()) + .userInfoUri(server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub") + .build(); + + + @Before + public void setup() { + super.setup(); + when(clientRegistrationRepository.findByRegistrationId(anyString())).thenAnswer(invocation -> { + String registrationId = invocation.getArgument(0); + return "cla-admin".equals(registrationId) ? this.claAdminRegistration : this.claUserRegistration; + }); + when(config.getGitHubApiBaseUrl()).thenReturn(server.getServer().url("/").toString()); + } + @Test public void webjarsPublic() throws Exception { mockMvc.perform(get("/webjars/bootstrap/css/bootstrap.min.css")) @@ -61,47 +96,25 @@ public void webjarsPublic() throws Exception { } @Test - public void requiresAuthenticationAndCreatesValidOAuthTokenRequest() throws Exception { - String redirect = mockMvc.perform(get("/sign/pivotal")) + public void requiresAuthenticationAndRedirectsForAuthorization() throws Exception { + mockMvc.perform(get("/sign/pivotal")) .andExpect(status().is3xxRedirection()) - .andReturn().getResponse().getRedirectedUrl(); - - UriComponents redirectComponent = UriComponentsBuilder.fromHttpUrl(redirect).build(); - - assertThat(redirectComponent.getScheme()).isEqualTo("https"); - assertThat(redirectComponent.getHost()).isEqualTo("github.com"); - MultiValueMap params = redirectComponent.getQueryParams(); - assertThat(params.getFirst("client_id")).isEqualTo(config.getMain().getClientId()); - assertThat(urlDecode(params.getFirst("redirect_uri"))).isEqualTo("http://localhost/login/oauth2/github"); - assertThat(params.getFirst("state")).isNotNull(); - - String[] scopes = urlDecode(params.getFirst("scope")).split(","); - assertThat(scopes).containsOnly("user:email"); + .andExpect(redirectedUrl("http://localhost/oauth2/authorization/cla-user")); } @Test - public void adminRequiresAuthenticationAndCreatesValidOAuthTokenRequest() throws Exception { - String redirect = mockMvc.perform(get("/admin/cla/link")).andExpect(status().is3xxRedirection()).andReturn() - .getResponse().getRedirectedUrl(); - - UriComponents redirectComponent = UriComponentsBuilder.fromHttpUrl(redirect).build(); - - assertThat(redirectComponent.getScheme()).isEqualTo("https"); - assertThat(redirectComponent.getHost()).isEqualTo("github.com"); - MultiValueMap params = redirectComponent.getQueryParams(); - assertThat(params.getFirst("client_id")).isEqualTo(config.getMain().getClientId()); - assertThat(urlDecode(params.getFirst("redirect_uri"))).isEqualTo("http://localhost/login/oauth2/github"); - assertThat(params.getFirst("state")).isNotNull(); - - String[] scopes = urlDecode(params.getFirst("scope")).split(","); - assertThat(scopes).containsOnly("user:email", "repo:status", "admin:repo_hook", "admin:org_hook", "read:org"); + public void adminRequiresAuthenticationAndRedirectsForAuthorization() throws Exception { + mockMvc.perform(get("/admin/cla/link")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost/oauth2/authorization/cla-admin")); } @Test + @EnqueueRequests({"getAccessToken", "getUserInfo", "notAuthor"}) public void savedRequestUsed() throws Exception { User user = WithAdminUserFactory.create(); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(user); + when(mockGitHub.getVerifiedEmails(any())).thenReturn(user.getEmails()); when(mockClaRepository.findAll()).thenReturn(Arrays.asList(cla)); AdminLinkClaPage page = AdminLinkClaPage.to(getDriver()); @@ -109,57 +122,51 @@ public void savedRequestUsed() throws Exception { } @Test + @EnqueueRequests({"getAccessToken", "getUserInfo"}) public void authenticateUser() throws Exception { User user = WithSigningUserFactory.create(); when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(user); + when(mockGitHub.getVerifiedEmails(any())).thenReturn(user.getEmails()); SignClaPage claPage = SignClaPage.go(driver, cla.getName()); claPage.assertAt(); - - ArgumentCaptor userCaptor = ArgumentCaptor.forClass(CurrentUserRequest.class); - verify(mockGitHub).getCurrentUser(userCaptor.capture()); - CurrentUserRequest userRequest = userCaptor.getValue(); - OAuthAccessTokenParams oauthParams = userRequest.getOauthParams(); - assertThat(userRequest.isRequestAdminAccess()).isFalse(); - assertThat(oauthParams.getCallbackUrl()).isEqualTo("https://localhost/login/oauth2/github"); - assertThat(oauthParams.getCode()).isEqualTo("abc"); } @Test + @EnqueueRequests({"getAccessToken", "getUserInfo", "notAuthor"}) public void authenticateAdmin() throws Exception { User user = WithAdminUserFactory.create(); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(user); + when(mockGitHub.getVerifiedEmails(any())).thenReturn(user.getEmails()); AdminLinkClaPage admin = AdminLinkClaPage.to(driver); admin.assertAt(); - - ArgumentCaptor userCaptor = ArgumentCaptor.forClass(CurrentUserRequest.class); - verify(mockGitHub).getCurrentUser(userCaptor.capture()); - CurrentUserRequest userRequest = userCaptor.getValue(); - OAuthAccessTokenParams oauthParams = userRequest.getOauthParams(); - assertThat(userRequest.isRequestAdminAccess()).isTrue(); - assertThat(oauthParams.getCallbackUrl()).isEqualTo("https://localhost/login/oauth2/github"); - assertThat(oauthParams.getCode()).isEqualTo("abc"); } @SuppressWarnings("unchecked") @Test + @EnqueueRequests({"getAccessToken", "getUserInfo", "notAuthor"}) public void userUrlWithAdminUserThenAdminUrl() throws Exception { + // Add scope 'repo:status' to enable admin user + ClientRegistration claUserRegistration = ClientRegistration.withRegistrationId("cla-user") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email", "repo:status") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri(server.getServer().url("/login/oauth/access_token").toString()) + .userInfoUri(server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub") + .build(); + when(clientRegistrationRepository.findByRegistrationId(anyString())).thenReturn(claUserRegistration); + User currentUser = WithAdminUserFactory.create(); - currentUser.setAdmin(false); when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenAnswer(new Answer() { - @Override - public User answer(InvocationOnMock invocation) throws Throwable { - CurrentUserRequest request = invocation.getArgument(0); - User currentUser = WithAdminUserFactory.create(); - currentUser.setAdmin(request.isRequestAdminAccess()); - return currentUser; - } - }); + when(mockGitHub.getVerifiedEmails(any())).thenReturn(currentUser.getEmails()); when(mockIndividualSignatureRepository.findByEmailIn(anySet())).thenReturn(Collections.emptyList()); @@ -171,32 +178,11 @@ public User answer(InvocationOnMock invocation) throws Throwable { AdminLinkClaPage admin = AdminLinkClaPage.to(driver); admin.assertAt(); - - ArgumentCaptor userCaptor = ArgumentCaptor.forClass(CurrentUserRequest.class); - verify(mockGitHub,times(2)).getCurrentUser(userCaptor.capture()); - assertThat(userCaptor.getAllValues()).extracting(CurrentUserRequest::isRequestAdminAccess).containsOnly(false, true); - } - - @Test - public void loginVerifiesSecretState() throws Exception { - User currentUser = WithAdminUserFactory.create(); - when(mockGitHub.getCurrentUser(any(CurrentUserRequest.class))).thenReturn(currentUser); - MockHttpSession session = new MockHttpSession(); - String redirect = mockMvc.perform(get("/sign/pivotal").session(session)) - .andExpect(status().is3xxRedirection()) - .andReturn().getResponse().getRedirectedUrl(); - - redirect = mockMvc.perform(get(redirect)) - .andReturn().getResponse().getRedirectedUrl(); - - // change the expected secret state - session.setAttribute("state", "INVALID"); - mockMvc.perform(get(redirect).session(session)) - .andExpect(status().isBadRequest()); } @Test @WithSigningUser + @EnqueueRequests({"getAccessToken", "getUserInfo"}) @SuppressWarnings("unchecked") public void signOut() throws Exception { when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); diff --git a/src/test/java/io/pivotal/cla/webdriver/BaseWebDriverTests.java b/src/test/java/io/pivotal/cla/webdriver/BaseWebDriverTests.java index 1ec11a6c..84a2eae3 100644 --- a/src/test/java/io/pivotal/cla/webdriver/BaseWebDriverTests.java +++ b/src/test/java/io/pivotal/cla/webdriver/BaseWebDriverTests.java @@ -23,6 +23,7 @@ import org.openqa.selenium.WebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder; @@ -57,6 +58,8 @@ public abstract class BaseWebDriverTests { protected AccessTokenRepository mockTokenRepo; @MockBean protected UserRepository mockUserRepo; + @MockBean + protected ClientRegistrationRepository clientRegistrationRepository; protected WebDriver driver; diff --git a/src/test/java/io/pivotal/cla/webdriver/ClaControllerTests.java b/src/test/java/io/pivotal/cla/webdriver/ClaControllerTests.java index a86ccc9e..a40e93cc 100644 --- a/src/test/java/io/pivotal/cla/webdriver/ClaControllerTests.java +++ b/src/test/java/io/pivotal/cla/webdriver/ClaControllerTests.java @@ -15,23 +15,6 @@ */ package io.pivotal.cla.webdriver; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.security.test.context.support.WithAnonymousUser; - import io.pivotal.cla.data.AccessToken; import io.pivotal.cla.data.User; import io.pivotal.cla.security.WithSigningUser; @@ -41,22 +24,61 @@ import io.pivotal.cla.webdriver.pages.SignCclaPage; import io.pivotal.cla.webdriver.pages.SignClaPage; import io.pivotal.cla.webdriver.pages.SignIclaPage; +import okhttp3.mockwebserver.EnqueueRequests; +import okhttp3.mockwebserver.EnqueueResourcesMockWebServer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.test.context.support.WithAnonymousUser; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WithSigningUser public class ClaControllerTests extends BaseWebDriverTests { + @Rule + public EnqueueResourcesMockWebServer server = new EnqueueResourcesMockWebServer(); + + private ClientRegistration claUserRegistration = ClientRegistration.withRegistrationId("cla-user") + .clientId("client-main-id-123") + .clientSecret("client-main-secret-abc") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUriTemplate("http://localhost/login/oauth2/github") + .scope("user:email") + .authorizationUri("https://github.com/login/oauth/authorize") + .tokenUri(server.getServer().url("/login/oauth/access_token").toString()) + .userInfoUri(server.getServer().url("/user").toString()) + .userNameAttributeName("id") + .clientName("GitHub") + .build(); + @Before public void setup() { super.setup(); when(mockClaRepository.findByNameAndPrimaryTrue(cla.getName())).thenReturn(cla); + when(clientRegistrationRepository.findByRegistrationId(claUserRegistration.getRegistrationId())).thenReturn(claUserRegistration); } @Test @WithAnonymousUser + @EnqueueRequests({"getAccessToken", "getUserInfo"}) public void viewSignedWithRepositoryIdAndPullRequestIdNewUser() throws Exception { String repositoryId = "spring-projects/spring-security"; User signingUser = WithSigningUserFactory.create(); - when(mockGitHub.getCurrentUser(any())).thenReturn(signingUser); + when(mockGitHub.getVerifiedEmails(any())).thenReturn(signingUser.getEmails()); when(mockGitHub.getShaForPullRequest(any(PullRequestStatus.class))).thenReturn("abc123"); when(mockIndividualSignatureRepository.findSignaturesFor(any(), eq(signingUser),eq(cla.getName()))).thenReturn(Arrays.asList(individualSignature)); when(mockIndividualSignatureRepository.findSignaturesFor(any(),eq(signingUser))).thenReturn(Arrays.asList(individualSignature)); diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 27972519..8fc1ba66 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -4,6 +4,13 @@ security.oauth2.admin.clientId=client-admin-id-123 security.oauth2.admin.clientSecret=client-admin-secret-abc security.oauth2.pivotal-cla.tokenSecret=pivotal_cla_mock_access_token +spring.security.oauth2.client.registration.cla-user.provider=github +spring.security.oauth2.client.registration.cla-user.clientId=${security.oauth2.main.clientId} +spring.security.oauth2.client.registration.cla-user.clientSecret=${security.oauth2.main.clientSecret} +spring.security.oauth2.client.registration.cla-user.redirectUriTemplate={baseUrl}/login/oauth2/github +spring.security.oauth2.client.registration.cla-user.scope=user:email +spring.security.oauth2.client.registration.cla-user.clientName=GitHub Mock + logging.level.root=ERROR logging.level.com.gargoylesoftware.htmlunit.javascript.StrictErrorReporter=OFF -spring.main.banner-mode=off \ No newline at end of file +spring.main.banner-mode=off diff --git a/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/getUserInfo b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/getUserInfo new file mode 100644 index 00000000..0cb78d9a --- /dev/null +++ b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/getUserInfo @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"id":"1234", "login":"rwinch", "name":"Rob Winch", "avatar_url":"https://avatars.githubusercontent.com/u/362503?v=3"} diff --git a/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/isAuthor b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/isAuthor new file mode 100644 index 00000000..eda54198 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/isAuthor @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"isAuthor":true} diff --git a/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/notAuthor b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/notAuthor new file mode 100644 index 00000000..c583c553 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/service/github/GitHubOAuth2UserServiceTests_okhttp3/notAuthor @@ -0,0 +1,4 @@ +HTTP/1.1 404 Not Found +Content-Type: application/json; charset=utf-8 + +{"isAuthor":false} diff --git a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getAccessToken b/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getAccessToken deleted file mode 100644 index c7184125..00000000 --- a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getAccessToken +++ /dev/null @@ -1,26 +0,0 @@ -HTTP/1.1 200 OK -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval -Cache-Control: private, max-age=60, s-maxage=60 -Content-Security-Policy: default-src 'none' -Content-Type: application/json; charset=utf-8 -Date: Sat, 05 Mar 2016 02:23:48 GMT -ETag: W/"3cc23789c35581fee740d53e76750ddc" -Server: GitHub.com -Status: 200 OK -Strict-Transport-Security: max-age=31536000; includeSubdomains; preload -Vary: Accept, Authorization, Cookie, X-GitHub-OTP -Vary: Accept-Encoding -X-Accepted-OAuth-Scopes: user, user:email -X-Content-Type-Options: nosniff -X-Frame-Options: deny -X-GitHub-Media-Type: github.v3 -X-GitHub-Request-Id: 4ADEC8ED:13E78:4A48235:56DA4334 -X-OAuth-Scopes: admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user -X-RateLimit-Limit: 5000 -X-RateLimit-Remaining: 4997 -X-RateLimit-Reset: 1457145439 -X-Served-By: bae57931a6fe678a3dffe9be8e7819c8 -X-XSS-Protection: 1; mode=block - -{"access_token":"access-token-123", "scope":"repo,gist", "token_type":"bearer"} diff --git a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getEmailsNotPivotal b/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getEmailsNotPivotal deleted file mode 100644 index 8728ef4e..00000000 --- a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getEmailsNotPivotal +++ /dev/null @@ -1,32 +0,0 @@ -HTTP/1.1 200 OK -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval -Cache-Control: private, max-age=60, s-maxage=60 -Content-Security-Policy: default-src 'none' -Content-Type: application/json; charset=utf-8 -Date: Sat, 05 Mar 2016 02:23:48 GMT -ETag: W/"3cc23789c35581fee740d53e76750ddc" -Server: GitHub.com -Status: 200 OK -Strict-Transport-Security: max-age=31536000; includeSubdomains; preload -Vary: Accept, Authorization, Cookie, X-GitHub-OTP -Vary: Accept-Encoding -X-Accepted-OAuth-Scopes: user, user:email -X-Content-Type-Options: nosniff -X-Frame-Options: deny -X-GitHub-Media-Type: github.v3 -X-GitHub-Request-Id: 4ADEC8ED:13E78:4A48235:56DA4334 -X-OAuth-Scopes: admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user -X-RateLimit-Limit: 5000 -X-RateLimit-Remaining: 4997 -X-RateLimit-Reset: 1457145439 -X-Served-By: bae57931a6fe678a3dffe9be8e7819c8 -X-XSS-Protection: 1; mode=block - -[ - { - "email": "rob@example.com", - "primary": true, - "verified": true - } -] \ No newline at end of file diff --git a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembers b/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembers deleted file mode 100644 index eb4e646c..00000000 --- a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembers +++ /dev/null @@ -1,30 +0,0 @@ -HTTP/1.1 200 OK -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval -Cache-Control: private, max-age=60, s-maxage=60 -Content-Security-Policy: default-src 'none' -Content-Type: application/json; charset=utf-8 -Date: Wed, 11 May 2016 13:34:30 GMT -ETag: W/"9f41291bea78e2da9efb1f08439b24f5" -Server: GitHub.com -Status: 200 OK -Strict-Transport-Security: max-age=31536000; includeSubdomains; preload -Vary: Accept, Authorization, Cookie, X-GitHub-OTP -Vary: Accept-Encoding -X-Accepted-OAuth-Scopes: admin:org, read:org, repo, write:org -X-Content-Type-Options: nosniff -X-Frame-Options: deny -X-GitHub-Media-Type: github.v3 -X-GitHub-Request-Id: 4ADEC8ED:2CBA:790793E:573334E6 -X-OAuth-Scopes: admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user -X-RateLimit-Limit: 5000 -X-RateLimit-Remaining: 4988 -X-RateLimit-Reset: 1462975735 -X-Served-By: cee4c0729c8e9147e7abcb45b9d69689 -X-XSS-Protection: 1; mode=block - -{ - "role": "member", - "state": "active", - "url": "https://api.github.com/teams/2006839/memberships/rwinch" -} \ No newline at end of file diff --git a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembersNotFound b/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembersNotFound deleted file mode 100644 index f5cfd58f..00000000 --- a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getTeamMembersNotFound +++ /dev/null @@ -1,25 +0,0 @@ -HTTP/1.1 404 Not Found -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval -Content-Encoding: gzip -Content-Security-Policy: default-src 'none' -Content-Type: application/json; charset=utf-8 -Date: Wed, 11 May 2016 13:59:13 GMT -Server: GitHub.com -Status: 404 Not Found -Strict-Transport-Security: max-age=31536000; includeSubdomains; preload -X-Accepted-OAuth-Scopes: admin:org, read:org, repo, user, write:org -X-Content-Type-Options: nosniff -X-Frame-Options: deny -X-GitHub-Media-Type: github.v3 -X-GitHub-Request-Id: 4ADEC8ED:2CBD:AB4492D:57333AB1 -X-OAuth-Scopes: admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user -X-RateLimit-Limit: 5000 -X-RateLimit-Remaining: 4964 -X-RateLimit-Reset: 1462975735 -X-XSS-Protection: 1; mode=block - -{ - "documentation_url": "https://developer.github.com/v3", - "message": "Not Found" -} \ No newline at end of file diff --git a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getUserRwinch b/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getUserRwinch deleted file mode 100644 index 40e775ac..00000000 --- a/src/test/resources/io/pivotal/cla/service/github/MylynGitHubApiITests_okhttp3/getUserRwinch +++ /dev/null @@ -1,69 +0,0 @@ -HTTP/1.1 200 OK -Access-Control-Allow-Origin: * -Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval -Cache-Control: private, max-age=60, s-maxage=60 -Content-Security-Policy: default-src 'none' -Content-Type: application/json; charset=utf-8 -Date: Sat, 05 Mar 2016 02:00:39 GMT -ETag: W/"e5665150505598abe7d231900bcc652d" -Last-Modified: Mon, 29 Feb 2016 19:32:24 GMT -Server: GitHub.com -Status: 200 OK -Strict-Transport-Security: max-age=31536000; includeSubdomains; preload -Vary: Accept, Authorization, Cookie, X-GitHub-OTP -Vary: Accept-Encoding -X-Accepted-OAuth-Scopes: -X-Content-Type-Options: nosniff -X-Frame-Options: deny -X-GitHub-Media-Type: github.v3 -X-GitHub-Request-Id: 4ADEC8ED:1D92B:4F6F06:56DA3DC7 -X-OAuth-Scopes: admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user -X-RateLimit-Limit: 5000 -X-RateLimit-Remaining: 4998 -X-RateLimit-Reset: 1457145439 -X-Served-By: a51acaae89a7607fd7ee967627be18e4 -X-XSS-Protection: 1; mode=block - -{ - "avatar_url": "https://avatars.githubusercontent.com/u/362503?v=3", - "bio": null, - "blog": "http://spring.io/team/rwinch", - "collaborators": 0, - "company": "Pivotal", - "created_at": "2010-08-12T18:08:04Z", - "disk_usage": 99308, - "email": null, - "events_url": "https://api.github.com/users/rwinch/events{/privacy}", - "followers": 282, - "followers_url": "https://api.github.com/users/rwinch/followers", - "following": 0, - "following_url": "https://api.github.com/users/rwinch/following{/other_user}", - "gists_url": "https://api.github.com/users/rwinch/gists{/gist_id}", - "gravatar_id": "", - "hireable": null, - "html_url": "https://github.com/rwinch", - "id": 362503, - "location": null, - "login": "rwinch", - "name": "Rob Winch", - "organizations_url": "https://api.github.com/users/rwinch/orgs", - "owned_private_repos": 0, - "plan": { - "collaborators": 0, - "name": "free", - "private_repos": 0, - "space": 976562499 - }, - "private_gists": 17, - "public_gists": 13, - "public_repos": 156, - "received_events_url": "https://api.github.com/users/rwinch/received_events", - "repos_url": "https://api.github.com/users/rwinch/repos", - "site_admin": false, - "starred_url": "https://api.github.com/users/rwinch/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/rwinch/subscriptions", - "total_private_repos": 0, - "type": "User", - "updated_at": "2016-02-29T19:32:24Z", - "url": "https://api.github.com/users/rwinch" -} \ No newline at end of file diff --git a/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getAccessToken b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getAccessToken new file mode 100644 index 00000000..0beaeeb2 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getAccessToken @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"access_token":"mocked_access_token", "token_type":"bearer", "expires_in":3600} diff --git a/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getUserInfo b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getUserInfo new file mode 100644 index 00000000..65d6b3e8 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/getUserInfo @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"id":"1234", "login":"rwinch", "avatar_url":"https://avatars.githubusercontent.com/u/362503?v=3"} diff --git a/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/notAuthor b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/notAuthor new file mode 100644 index 00000000..c583c553 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/webdriver/AuthenticationTests_okhttp3/notAuthor @@ -0,0 +1,4 @@ +HTTP/1.1 404 Not Found +Content-Type: application/json; charset=utf-8 + +{"isAuthor":false} diff --git a/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getAccessToken b/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getAccessToken new file mode 100644 index 00000000..0beaeeb2 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getAccessToken @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"access_token":"mocked_access_token", "token_type":"bearer", "expires_in":3600} diff --git a/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getUserInfo b/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getUserInfo new file mode 100644 index 00000000..07fbc5e2 --- /dev/null +++ b/src/test/resources/io/pivotal/cla/webdriver/ClaControllerTests_okhttp3/getUserInfo @@ -0,0 +1,4 @@ +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 + +{"id":"1234", "login":"robwinch", "avatar_url":"https://avatars.githubusercontent.com/u/362503?v=3"}