diff --git a/common.iml b/common.iml new file mode 100644 index 0000000..66b3d79 --- /dev/null +++ b/common.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/oauth-provider-api/oauth-provider-api.iml b/oauth-provider-api/oauth-provider-api.iml index 1009c43..b82857a 100644 --- a/oauth-provider-api/oauth-provider-api.iml +++ b/oauth-provider-api/oauth-provider-api.iml @@ -5,20 +5,17 @@ - - + - - - - - - + + + + diff --git a/oauth-provider-api/pom.xml b/oauth-provider-api/pom.xml index 66ea3c0..aaa56ec 100644 --- a/oauth-provider-api/pom.xml +++ b/oauth-provider-api/pom.xml @@ -23,6 +23,11 @@ feign-core ${feign.version} + + org.hibernate + hibernate-validator + ${hibernate-validator.version} + com.fasterxml.jackson.core jackson-databind diff --git a/oauth-provider-api/src/main/java/de/helfenkannjeder/oauth/provider/api/dto/UserRequestDto.java b/oauth-provider-api/src/main/java/de/helfenkannjeder/oauth/provider/api/dto/UserRequestDto.java index 557459c..0855c06 100644 --- a/oauth-provider-api/src/main/java/de/helfenkannjeder/oauth/provider/api/dto/UserRequestDto.java +++ b/oauth-provider-api/src/main/java/de/helfenkannjeder/oauth/provider/api/dto/UserRequestDto.java @@ -1,11 +1,20 @@ package de.helfenkannjeder.oauth.provider.api.dto; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.NotNull; + /** * @author Valentin Zickner */ public class UserRequestDto { + @NotNull + @NotEmpty private String username; + + @NotNull + @NotEmpty private String password; public UserRequestDto() { diff --git a/oauth-provider/oauth-provider.iml b/oauth-provider/oauth-provider.iml index 308bafe..c7b5969 100644 --- a/oauth-provider/oauth-provider.iml +++ b/oauth-provider/oauth-provider.iml @@ -1,14 +1,28 @@ - + + + + + spring_boot_de.helfenkannjeder.oauth.provider.configuration.OAuth2ProviderApplication + file://$MODULE_DIR$/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderApplication.java + file://$MODULE_DIR$/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderConfiguration.java + file://$MODULE_DIR$/src/main/java/de/helfenkannjeder/oauth/provider/configuration/SecurityConfiguration.java + + + + + + + - + @@ -17,11 +31,8 @@ - - - @@ -29,19 +40,69 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oauth-provider/pom.xml b/oauth-provider/pom.xml index 85a97d7..4744a12 100644 --- a/oauth-provider/pom.xml +++ b/oauth-provider/pom.xml @@ -6,6 +6,13 @@ de.helfenkannjeder.common oauth-provider + + 1.8 + 0.0.1-SNAPSHOT + 4.12 + 1.4.182 + + org.springframework.boot spring-boot-starter-parent @@ -19,5 +26,54 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.security.oauth + spring-security-oauth2 + + + de.helfenkannjeder.common + oauth-provider-api + ${common.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + + + mysql + mysql-connector-java + runtime + + + org.hibernate + hibernate-validator + + + com.h2database + h2 + ${h2.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + junit + junit + ${junit.version} + test + + + com.jayway.jsonpath + json-path + test + diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/SampleController.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/SampleController.java deleted file mode 100644 index 4dc88ce..0000000 --- a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/SampleController.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.helfenkannjeder.oauth.provider; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -/** - * @author Valentin Zickner - */ -@Controller -@EnableAutoConfiguration -public class SampleController { - - @RequestMapping("/") - @ResponseBody - String home() { - return "Hello World!"; - } - - public static void main(String[] args) throws Exception { - SpringApplication.run(SampleController.class, args); - } -} \ No newline at end of file diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderApplication.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderApplication.java new file mode 100644 index 0000000..ec7c98a --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderApplication.java @@ -0,0 +1,23 @@ +package de.helfenkannjeder.oauth.provider.configuration; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.orm.jpa.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; + +/** + * @author Valentin Zickner + */ +@SpringBootApplication +@EnableAuthorizationServer +@EntityScan(basePackages = "de.helfenkannjeder.oauth.provider.domain") +@EnableJpaRepositories("de.helfenkannjeder.oauth.provider.domain.repository") +@ComponentScan(basePackages = "de.helfenkannjeder.oauth.provider") +public class OAuth2ProviderApplication { + public static void main(String[] args) { + SpringApplication.run(OAuth2ProviderApplication.class, args); + } + +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderConfiguration.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderConfiguration.java new file mode 100644 index 0000000..320a84f --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/OAuth2ProviderConfiguration.java @@ -0,0 +1,100 @@ +package de.helfenkannjeder.oauth.provider.configuration; + +import de.helfenkannjeder.oauth.provider.security.OAuthProviderAuthority; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +/** + * @author Valentin Zickner + */ +@Configuration +public class OAuth2ProviderConfiguration extends AuthorizationServerConfigurerAdapter { + + public static final String CLIENT_CREDENTIALS = "client_credentials"; + public static final String AUTHORIZATION_CODE = "authorization_code"; + public static final String PASSWORD = "password"; + public static final String SCOPE_DEFAULT = "default"; + public static final String REFRESH_TOKEN = "refresh_token"; + + private TokenStore tokenStore = new InMemoryTokenStore(); + + @Value("${oauth.client.admin.clientId}") + private String adminClientId; + + @Value("${oauth.client.admin.secret}") + private String adminSecret; + + @Value("${oauth.client.come2help.clientId}") + private String come2helpClientId; + + @Value("${oauth.client.come2help.secret}") + private String come2helpClientSecret; + + @Autowired + @Qualifier("authenticationManagerBean") + private AuthenticationManager authenticationManager; + + @Autowired + private UserDetailsService userDetailsService; + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + // @formatter:off + clients.inMemory() + .withClient(adminClientId) + .secret(adminSecret) + .authorizedGrantTypes(CLIENT_CREDENTIALS, REFRESH_TOKEN) + .scopes(SCOPE_DEFAULT) + .authorities(OAuthProviderAuthority.ROLE_ADMIN.getAuthority(), + OAuthProviderAuthority.ROLE_USER.getAuthority()) + .and() + .withClient(come2helpClientId) + .secret(come2helpClientSecret) + .authorizedGrantTypes(AUTHORIZATION_CODE, PASSWORD, REFRESH_TOKEN) + .scopes(SCOPE_DEFAULT) + .authorities(OAuthProviderAuthority.ROLE_USER.getAuthority()); + // @formatter:on + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints + .authenticationManager(authenticationManager) + .userDetailsService(userDetailsService) + .tokenStore(tokenStore); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.allowFormAuthenticationForClients(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Primary + public DefaultTokenServices tokenServices() { + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setSupportRefreshToken(true); + tokenServices.setTokenStore(this.tokenStore); + return tokenServices; + } +} \ No newline at end of file diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/ResourceServerConfiguration.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/ResourceServerConfiguration.java new file mode 100644 index 0000000..f3b6f5e --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/ResourceServerConfiguration.java @@ -0,0 +1,22 @@ +package de.helfenkannjeder.oauth.provider.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; + +/** + * @author Valentin Zickner + */ +@Configuration +@EnableResourceServer +public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { + @Override + public void configure(HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN") + .antMatchers("/user/**").authenticated(); + // @formatter:on + } +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/WebSecurityConfiguration.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/WebSecurityConfiguration.java new file mode 100644 index 0000000..2442aa9 --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/configuration/WebSecurityConfiguration.java @@ -0,0 +1,48 @@ +package de.helfenkannjeder.oauth.provider.configuration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * @author Valentin Zickner + */ +@Configuration +@EnableWebSecurity +@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) +public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + +} \ No newline at end of file diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/OAuthUser.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/OAuthUser.java new file mode 100644 index 0000000..7594b0f --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/OAuthUser.java @@ -0,0 +1,50 @@ +package de.helfenkannjeder.oauth.provider.domain; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +/** + * @author Valentin Zickner + */ +@Entity +public class OAuthUser { + + @Id + @GeneratedValue + private Long id; + private String username; + private String password; + + public OAuthUser() { + } + + public OAuthUser(String username, String password) { + this.username = username; + this.password = password; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/repository/OAuthUserRepository.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/repository/OAuthUserRepository.java new file mode 100644 index 0000000..344ef9d --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/domain/repository/OAuthUserRepository.java @@ -0,0 +1,11 @@ +package de.helfenkannjeder.oauth.provider.domain.repository; + +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Valentin Zickner + */ +public interface OAuthUserRepository extends CrudRepository { + OAuthUser findOneByUsernameIgnoreCase(String username); +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/exception/UsernameAlreadyExistsException.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/exception/UsernameAlreadyExistsException.java new file mode 100644 index 0000000..3dccffb --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/exception/UsernameAlreadyExistsException.java @@ -0,0 +1,11 @@ +package de.helfenkannjeder.oauth.provider.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author Valentin Zickner + */ +@ResponseStatus(value = HttpStatus.CONFLICT, reason = "Username already exists.") +public class UsernameAlreadyExistsException extends RuntimeException { +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserController.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserController.java new file mode 100644 index 0000000..93836b5 --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserController.java @@ -0,0 +1,20 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +/** + * @author Valentin Zickner + */ +@RestController +public class UserController { + + @RequestMapping("/user/information") + public OAuthUser currentUser(Principal user) { + return new OAuthUser(user.getName(), null); + } + +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserManagementController.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserManagementController.java new file mode 100644 index 0000000..9bf0d9f --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/rest/UserManagementController.java @@ -0,0 +1,66 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import de.helfenkannjeder.oauth.provider.api.OAuthProviderUserManagementApi; +import de.helfenkannjeder.oauth.provider.api.dto.UserRequestDto; +import de.helfenkannjeder.oauth.provider.api.dto.UserResponseDto; +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import de.helfenkannjeder.oauth.provider.domain.repository.OAuthUserRepository; +import de.helfenkannjeder.oauth.provider.exception.UsernameAlreadyExistsException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * @author Valentin Zickner + */ +@RestController +@RequestMapping("/admin") +public class UserManagementController implements OAuthProviderUserManagementApi { + + @Autowired + private OAuthUserRepository oAuthUserRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + @RequestMapping(value = CREATE, method = RequestMethod.POST) + public UserResponseDto create(@Valid @RequestBody UserRequestDto userRequestDto) { + assertUserDoesNotExists(userRequestDto); + + String password = passwordEncoder.encode(userRequestDto.getPassword()); + OAuthUser user = oAuthUserRepository.save(new OAuthUser(userRequestDto.getUsername(), password)); + return new UserResponseDto(String.valueOf(user.getId())); + } + + @Override + @RequestMapping(value = UPDATE, method = RequestMethod.PUT) + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@PathVariable("id") String id, @Valid @RequestBody UserRequestDto userRequestDto) { + OAuthUser user = oAuthUserRepository.findOne(Long.valueOf(id)); + + if (!user.getUsername().equals(userRequestDto.getUsername())) { + assertUserDoesNotExists(userRequestDto); + } + + user.setUsername(userRequestDto.getUsername()); + user.setPassword(passwordEncoder.encode(userRequestDto.getPassword())); + oAuthUserRepository.save(user); + } + + private void assertUserDoesNotExists(@RequestBody UserRequestDto userRequestDto) { + if (oAuthUserRepository.findOneByUsernameIgnoreCase(userRequestDto.getUsername()) != null) { + throw new UsernameAlreadyExistsException(); + } + } + + @Override + @RequestMapping(value = DELETE, method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable("id") String id) { + oAuthUserRepository.delete(Long.valueOf(id)); + } +} \ No newline at end of file diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthProviderAuthority.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthProviderAuthority.java new file mode 100644 index 0000000..b0e0f42 --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthProviderAuthority.java @@ -0,0 +1,14 @@ +package de.helfenkannjeder.oauth.provider.security; + +import org.springframework.security.core.GrantedAuthority; + +/** + * @author Valentin Zickner + */ +public enum OAuthProviderAuthority implements GrantedAuthority { + ROLE_USER, ROLE_ADMIN; + + public String getAuthority() { + return name(); + } +} diff --git a/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthUserDetailsService.java b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthUserDetailsService.java new file mode 100644 index 0000000..5946e40 --- /dev/null +++ b/oauth-provider/src/main/java/de/helfenkannjeder/oauth/provider/security/OAuthUserDetailsService.java @@ -0,0 +1,36 @@ +package de.helfenkannjeder.oauth.provider.security; + +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import de.helfenkannjeder.oauth.provider.domain.repository.OAuthUserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +/** + * @author Valentin Zickner + */ +@Service +public class OAuthUserDetailsService implements UserDetailsService { + + private final OAuthUserRepository oAuthUserRepository; + + @Autowired + public OAuthUserDetailsService(OAuthUserRepository oAuthUserRepository) { + this.oAuthUserRepository = oAuthUserRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + OAuthUser user = oAuthUserRepository.findOneByUsernameIgnoreCase(username); + if (user == null) { + return null; + } + return new User(user.getUsername(), user.getPassword(), Collections.singleton(OAuthProviderAuthority.ROLE_USER)); + } + +} diff --git a/oauth-provider/src/main/resources/application.yml b/oauth-provider/src/main/resources/application.yml index c2eee0d..1a35644 100644 --- a/oauth-provider/src/main/resources/application.yml +++ b/oauth-provider/src/main/resources/application.yml @@ -1 +1,30 @@ -server.port: 8081 \ No newline at end of file +server.port: 8081 + +# =============================== +# = DATA SOURCE +# =============================== +spring: + datasource: + url: jdbc:mysql://localhost:3306/oauth + username: oauth + password: ZouNjsMYoll7eGHVWm4xHp75nabi4tqdvQTrUQTD6PVjodXy8UlgIXkpA2G2 + + testWhileIdle: true + validationQuery: SELECT 1 + + jpa: + show-sql: true + hibernate: + ddl-auto: update + naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL5Dialect + +oauth.client: + admin: + clientId: oauth-provider-admin + secret: default-secret + come2help: + clientId: come2help-web + secret: secret \ No newline at end of file diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/MockMvcOAuthLogin.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/MockMvcOAuthLogin.java new file mode 100644 index 0000000..d290c88 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/MockMvcOAuthLogin.java @@ -0,0 +1,184 @@ +package de.helfenkannjeder.oauth.provider; + +import de.helfenkannjeder.oauth.provider.configuration.OAuth2ProviderConfiguration; +import org.codehaus.jackson.map.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.web.csrf.DefaultCsrfToken; +import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; +import org.springframework.stereotype.Service; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.Base64Utils; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.number.OrderingComparison.greaterThan; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * @author Valentin Zickner + */ +@Service +public class MockMvcOAuthLogin { + + public static final String ENDPOINT_OAUTH_TOKEN = "/oauth/token"; + public static final String ENDPOINT_OAUTH_AUTHORIZATION = "/oauth/authorize"; + public static final String ENDPOINT_CONFIRM_ACCESS = "/oauth/confirm_access"; + + private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class + .getName().concat(".CSRF_TOKEN"); + + + private WebApplicationContext webApplicationContext; + private MockMvc mockMvc; + + @Autowired + public MockMvcOAuthLogin(WebApplicationContext webApplicationContext, Filter springSecurityFilterChain) { + this.webApplicationContext = webApplicationContext; + this.mockMvc = MockMvcBuilders + .webAppContextSetup(this.webApplicationContext) + .addFilters(springSecurityFilterChain) + .build(); + + } + + public OAuthInformation getAccessTokenWithClientSecret(String clientId, String secret) throws Exception { + return getAccessToken(post(ENDPOINT_OAUTH_TOKEN), OAuth2ProviderConfiguration.CLIENT_CREDENTIALS, clientId, secret); + } + + public OAuthInformation getAccessTokenWithAuthorizationCode(String clientId, String secret, String username, String password) throws Exception { + MockHttpSession mockSession = new MockHttpSession(webApplicationContext.getServletContext(), UUID.randomUUID().toString()); + + String authorization = createBase64Auth(username, password); + ResultActions query = mockMvc.perform( + get(ENDPOINT_OAUTH_AUTHORIZATION) + .session(mockSession) + .header("Authorization", authorization) + .param("response_type", "code") + .param("redirect_uri", "/") + .param("client_id", clientId) + ); + MockHttpServletResponse response = query.andReturn().getResponse(); + + // needs to confirm access + if (response.getStatus() == 200) { + query.andExpect(forwardedUrl(ENDPOINT_CONFIRM_ACCESS)); + + // Collect CSRF token from session + mockMvc.perform(get(ENDPOINT_CONFIRM_ACCESS) + .session(mockSession) + .header("Authorization", authorization) + ) + .andExpect(status().isOk()); + + DefaultCsrfToken csrfToken = (DefaultCsrfToken) mockSession.getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME); + + query = mockMvc.perform( + post(ENDPOINT_OAUTH_AUTHORIZATION) + .session(mockSession) + .header("Authorization", authorization) + .header(csrfToken.getHeaderName(), csrfToken.getToken()) + .param("scope.default", "true") + .param("user_oauth_approval", "true") + .param("authorize", "Authorize") + ); + } + + response = query + .andExpect(status().is3xxRedirection()) + .andReturn().getResponse(); + + String content = response.getRedirectedUrl(); + String code = content.split("=")[1]; + return getAccessTokenWithAuthorizationCode(clientId, secret, code); + } + + private OAuthInformation getAccessTokenWithAuthorizationCode(String clientId, String secret, String code) throws Exception { + MockHttpServletRequestBuilder post = post(ENDPOINT_OAUTH_TOKEN) + .param("code", code) + .param("redirect_uri", "/"); + return getAccessToken(post, OAuth2ProviderConfiguration.AUTHORIZATION_CODE, clientId, secret); + } + + public OAuthInformation getAccessTokenWithPassword(String clientId, String secret, String username, String password) throws Exception { + MockHttpServletRequestBuilder post = post(ENDPOINT_OAUTH_TOKEN) + .param("username", username) + .param("password", password); + return getAccessToken(post, OAuth2ProviderConfiguration.PASSWORD, clientId, secret); + } + + public OAuthInformation getAccessTokenWithRefreshToken(String clientId, String secret, String refreshToken) throws Exception { + MockHttpServletRequestBuilder post = post(ENDPOINT_OAUTH_TOKEN) + .param("refresh_token", refreshToken); + return getAccessToken(post, OAuth2ProviderConfiguration.REFRESH_TOKEN, clientId, secret); + } + + /* + * Original from https://github.com/royclarkson/spring-rest-service-oauth/blob/master/src/test/java/hello/GreetingControllerTest.java + */ + private OAuthInformation getAccessToken(MockHttpServletRequestBuilder post, String grantType, String clientId, String secret) throws Exception { + + // @formatter:off + String content = mockMvc.perform(post + .header("Authorization", createBase64Auth(clientId, secret)) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", grantType) + .param("scope", OAuth2ProviderConfiguration.SCOPE_DEFAULT)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$.access_token", is(notNullValue()))) + .andExpect(jsonPath("$.token_type", is(equalTo("bearer")))) + .andExpect(jsonPath("$.expires_in", is(greaterThan(4000)))) + .andReturn().getResponse().getContentAsString(); + // @formatter:on + + ObjectMapper objectMapper = new ObjectMapper(); + HashMap result = objectMapper.readValue(content, HashMap.class); + + return new OAuthInformation( + (String) result.get("access_token"), + (String) result.get("refresh_token") + ); + } + + private String createBase64Auth(String username, String password) { + return "Basic " + new String(Base64Utils.encode((username + ":" + password).getBytes())); + } + + + public static class OAuthInformation { + private final String accessToken; + private final String refreshToken; + + public OAuthInformation(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public String getAuthorization() { + return "Bearer " + getAccessToken(); + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + } + +} diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/AbstractOAuthControllerTest.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/AbstractOAuthControllerTest.java new file mode 100644 index 0000000..ab67281 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/AbstractOAuthControllerTest.java @@ -0,0 +1,69 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import de.helfenkannjeder.oauth.provider.MockMvcOAuthLogin; +import de.helfenkannjeder.oauth.provider.configuration.OAuth2ProviderApplication; +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import de.helfenkannjeder.oauth.provider.domain.repository.OAuthUserRepository; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.Filter; + +/** + * @author Valentin Zickner + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebIntegrationTest(randomPort = true) +@SpringApplicationConfiguration(classes = OAuth2ProviderApplication.class) +public abstract class AbstractOAuthControllerTest { + + public static final String DEFAULT_USER = "my-default-user"; + public static final String DEFAULT_PASSWORD = "my-default-password"; + + @Autowired + protected OAuthUserRepository oAuthUserRepository; + + MockMvc mockMvc; + + @Autowired + PasswordEncoder passwordEncoder; + + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private Filter springSecurityFilterChain; + + @Autowired + MockMvcOAuthLogin mockMvcOAuthLogin; + + @Before + public void initMockMvc() throws Exception { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(this.webApplicationContext) + .addFilters(springSecurityFilterChain) + .build(); + + oAuthUserRepository.save(new OAuthUser(AbstractOAuthControllerTest.DEFAULT_USER, passwordEncoder.encode(AbstractOAuthControllerTest.DEFAULT_PASSWORD))); + } + + protected String getAuthorizationAdmin() throws Exception { + return mockMvcOAuthLogin.getAccessTokenWithClientSecret("oauth-provider-admin", "default-secret").getAuthorization(); + } + + protected String getAuthorizationDefaultUser() throws Exception { + return mockMvcOAuthLogin.getAccessTokenWithAuthorizationCode("come2help-web", "secret", AbstractOAuthControllerTest.DEFAULT_USER, AbstractOAuthControllerTest.DEFAULT_PASSWORD).getAuthorization(); + } + + protected String getAuthorizationDefaultUserWithPassword() throws Exception { + return mockMvcOAuthLogin.getAccessTokenWithPassword("come2help-web", "secret", AbstractOAuthControllerTest.DEFAULT_USER, AbstractOAuthControllerTest.DEFAULT_PASSWORD).getAuthorization(); + } +} diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/SimpleAuthorizationTest.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/SimpleAuthorizationTest.java new file mode 100644 index 0000000..20ca103 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/SimpleAuthorizationTest.java @@ -0,0 +1,52 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import de.helfenkannjeder.oauth.provider.MockMvcOAuthLogin; +import org.junit.Test; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Valentin Zickner + */ +@Transactional +@DirtiesContext +public class SimpleAuthorizationTest extends AbstractOAuthControllerTest { + + @Test + public void testAuthorizationAdmin() throws Exception { + getAuthorizationAdmin(); + } + + @Test + public void testAuthorizationDefaultUser() throws Exception { + getAuthorizationDefaultUser(); + } + + @Test + public void testAuthorizationDefaultUserWithPassword() throws Exception { + getAuthorizationDefaultUserWithPassword(); + } + + @Test + public void loginAuthorizationEndpoint_withoutPassword_returnsUnauthorized() throws Exception { + // Act + mockMvc.perform(get(MockMvcOAuthLogin.ENDPOINT_OAUTH_AUTHORIZATION) + .param("response_type", "code") + .param("redirect_uri", "/") + .param("client_id", "come2help-web") + ) + .andExpect(status().isUnauthorized()); + } + + @Test + public void refreshToken_withRefreshToken_returnsNewToken() throws Exception { + // Arrange + MockMvcOAuthLogin.OAuthInformation oAuthInformation = mockMvcOAuthLogin.getAccessTokenWithPassword("come2help-web", "secret", AbstractOAuthControllerTest.DEFAULT_USER, AbstractOAuthControllerTest.DEFAULT_PASSWORD); + + // Act + Assert + mockMvcOAuthLogin.getAccessTokenWithRefreshToken("come2help-web", "secret", oAuthInformation.getRefreshToken()); + } +} diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserControllerTest.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserControllerTest.java new file mode 100644 index 0000000..3de42d0 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserControllerTest.java @@ -0,0 +1,56 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * @author Valentin Zickner + */ +@Transactional +@DirtiesContext +public class UserControllerTest extends AbstractOAuthControllerTest { + + @Test + public void currentUser_withNormalUser_expectUsername() throws Exception { + // Act + Assert + this.mockMvc.perform(get("/user/information") + .header("Authorization", getAuthorizationDefaultUser()) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$").isMap()) + .andExpect(jsonPath("$.username").value(DEFAULT_USER)); + } + + @Test + public void currentUser_withNormalUserAndPasswordAuthentication_expectUsername() throws Exception { + // Act + Assert + this.mockMvc.perform(get("/user/information") + .header("Authorization", getAuthorizationDefaultUserWithPassword()) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$").isMap()) + .andExpect(jsonPath("$.username").value(DEFAULT_USER)); + } + + @Test + public void currentUser_withAdmin_expectNoUsername() throws Exception { + // Act + Assert + this.mockMvc.perform(get("/user/information") + .header("Authorization", getAuthorizationAdmin()) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$").isMap()) + .andExpect(jsonPath("$.username").value("oauth-provider-admin")); + } +} \ No newline at end of file diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserManagementControllerTest.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserManagementControllerTest.java new file mode 100644 index 0000000..7376912 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/rest/UserManagementControllerTest.java @@ -0,0 +1,262 @@ +package de.helfenkannjeder.oauth.provider.rest; + +import de.helfenkannjeder.oauth.provider.api.OAuthProviderUserManagementApi; +import de.helfenkannjeder.oauth.provider.api.dto.UserRequestDto; +import de.helfenkannjeder.oauth.provider.api.dto.UserResponseDto; +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import org.codehaus.jackson.map.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.transaction.annotation.Transactional; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * @author Valentin Zickner + */ +@Transactional +@DirtiesContext +public class UserManagementControllerTest extends AbstractOAuthControllerTest { + + public static final String RESOURCE_PREFIX = "/admin"; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private UserRequestDto userRequestDto; + + @Before + public void setUp() throws Exception { + userRequestDto = new UserRequestDto("my-user", "my-password"); + } + + @Test + public void create_withAdminAndUsernameAndPassword_returnsNewUserId() throws Exception { + // Act + MvcResult result = this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(jsonPath("$").isMap()) + .andExpect(jsonPath("$.id").isNotEmpty()) + .andReturn(); + + // Assert + assertNotNull(result); + UserResponseDto userResponseDto = objectMapper.readValue(result.getResponse().getContentAsString(), UserResponseDto.class); + String userId = userResponseDto.getId(); + assertNotNull(userId); + OAuthUser oAuthUser = oAuthUserRepository.findOne(Long.valueOf(userId)); + assertEquals(userRequestDto.getUsername(), oAuthUser.getUsername()); + assertTrue(passwordEncoder.matches(userRequestDto.getPassword(), oAuthUser.getPassword())); + } + + @Test + public void create_withAdminAndWithoutContent_returnsNoNewUser() throws Exception { + // Act + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content("{}") + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isBadRequest()); + } + + @Test + public void create_withAdminAndWithEmptyUsernameAndPassword_returnsNoNewUser() throws Exception { + // Act + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content("{\"username\":\"\",\"password\":\"\"}") + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isBadRequest()); + } + + @Test + public void create_withAdminAndDuplicateUsernameInLowerCase_returnsConflict() throws Exception { + // Arrange + userRequestDto.setUsername(DEFAULT_USER.toLowerCase()); + + // Act + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + + // Assert + .andExpect(status().isConflict()); + } + + @Test + public void create_withAdminAndDuplicateUsernameInUpperCase_returnsConflict() throws Exception { + // Arrange + userRequestDto.setUsername(DEFAULT_USER.toUpperCase()); + + // Act + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + + // Assert + .andExpect(status().isConflict()); + } + + @Test + public void create_withNotAdmin_returns403() throws Exception { + // Act + Assert + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .header("Authorization", getAuthorizationDefaultUser()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ).andExpect(status().isForbidden()); + } + + @Test + public void create_withNoAuthentication_returns401() throws Exception { + // Act + Assert + this.mockMvc.perform(post(RESOURCE_PREFIX + OAuthProviderUserManagementApi.CREATE) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ).andExpect(status().isUnauthorized()); + } + + @Test + public void update_withAdminLoggedInAndNewUserInformation_verifyUserIsChanged() throws Exception { + // Arrange + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + + // Act + this.mockMvc.perform(put(RESOURCE_PREFIX + OAuthProviderUserManagementApi.UPDATE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isNoContent()); + + // Assert + OAuthUser oAuthUser = oAuthUserRepository.findOne(userId); + assertEquals(userRequestDto.getUsername(), oAuthUser.getUsername()); + assertTrue(passwordEncoder.matches(userRequestDto.getPassword(), oAuthUser.getPassword())); + } + + @Test + public void update_withAdminLoggedInAndAlreadyExistingUsername_verifyUserIsNotChanged() throws Exception { + // Arrange + oAuthUserRepository.save(new OAuthUser(userRequestDto.getUsername(), "password")); + + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + + // Act + this.mockMvc.perform(put(RESOURCE_PREFIX + OAuthProviderUserManagementApi.UPDATE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isConflict()); + + // Assert + assertUserUnchanged(userId); + } + + private void assertUserUnchanged(Long userId) { + OAuthUser oAuthUser = oAuthUserRepository.findOne(userId); + assertEquals(DEFAULT_USER, oAuthUser.getUsername()); + assertTrue(passwordEncoder.matches(DEFAULT_PASSWORD, oAuthUser.getPassword())); + } + + @Test + public void update_withAdminLoggedInOnlyChangingPassword_verifyUserIsChanged() throws Exception { + // Arrange + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + userRequestDto.setUsername(DEFAULT_USER); + + // Act + this.mockMvc.perform(put(RESOURCE_PREFIX + OAuthProviderUserManagementApi.UPDATE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(userRequestDto)) + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isNoContent()); + + // Assert + OAuthUser oAuthUser = oAuthUserRepository.findOne(userId); + assertEquals(DEFAULT_USER, oAuthUser.getUsername()); + assertTrue(passwordEncoder.matches(userRequestDto.getPassword(), oAuthUser.getPassword())); + } + + + @Test + public void update_withAdminLoggedInEmptyObject_verifyUserIsNotChanged() throws Exception { + // Arrange + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + + // Act + this.mockMvc.perform(put(RESOURCE_PREFIX + OAuthProviderUserManagementApi.UPDATE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationAdmin()) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content("{}") + .accept(MediaType.APPLICATION_JSON_UTF8) + ) + .andExpect(status().isBadRequest()); + + // Assert + assertUserUnchanged(userId); + } + + @Test + public void delete_withAdminLoggedIn_verifyUserIsRemoved() throws Exception { + // Arrange + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + + // Act + this.mockMvc.perform(delete(RESOURCE_PREFIX + OAuthProviderUserManagementApi.DELETE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationAdmin()) + ) + .andExpect(status().isNoContent()); + + // Assert + OAuthUser oAuthUser = oAuthUserRepository.findOne(userId); + assertNull(oAuthUser); + } + + @Test + public void delete_withNormalUserLoggedIn_verifyUserIsNotRemoved() throws Exception { + // Arrange + Long userId = oAuthUserRepository.findOneByUsernameIgnoreCase(DEFAULT_USER).getId(); + + // Act + this.mockMvc.perform(delete(RESOURCE_PREFIX + OAuthProviderUserManagementApi.DELETE.replace("{id}", userId.toString())) + .header("Authorization", getAuthorizationDefaultUser()) + ) + .andExpect(status().isForbidden()); + + // Assert + assertUserUnchanged(userId); + } +} \ No newline at end of file diff --git a/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/service/OAuthUserDetailsServiceTest.java b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/service/OAuthUserDetailsServiceTest.java new file mode 100644 index 0000000..ef7bc65 --- /dev/null +++ b/oauth-provider/src/test/java/de/helfenkannjeder/oauth/provider/service/OAuthUserDetailsServiceTest.java @@ -0,0 +1,46 @@ +package de.helfenkannjeder.oauth.provider.service; + +import de.helfenkannjeder.oauth.provider.domain.OAuthUser; +import de.helfenkannjeder.oauth.provider.domain.repository.OAuthUserRepository; +import de.helfenkannjeder.oauth.provider.security.OAuthUserDetailsService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.core.userdetails.UserDetails; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +/** + * @author Valentin Zickner + */ +@RunWith(MockitoJUnitRunner.class) +public class OAuthUserDetailsServiceTest { + + private OAuthUserDetailsService oAuthUserDetailsService; + + @Mock + private OAuthUserRepository oAuthUserRepository; + + @Before + public void setUp() throws Exception { + when(oAuthUserRepository.findOneByUsernameIgnoreCase("my-user")).thenReturn(new OAuthUser("my-user", "$2a$10$mXEdVKm16/vj/JyE.MgQ..UBa0p4rF1JYeGvLzvOJykact6UPVRx.")); + oAuthUserDetailsService = new OAuthUserDetailsService(oAuthUserRepository); + } + + @Test + public void loadUserByUsername_withMockedUser_verifyUserDetails() throws Exception { + // Arrange + + // Act + UserDetails userDetails = oAuthUserDetailsService.loadUserByUsername("my-user"); + + // Assert + assertNotNull(userDetails); + assertEquals("my-user", userDetails.getUsername()); + assertEquals("$2a$10$mXEdVKm16/vj/JyE.MgQ..UBa0p4rF1JYeGvLzvOJykact6UPVRx.", userDetails.getPassword()); + } +} \ No newline at end of file diff --git a/oauth-provider/src/test/resources/application.yml b/oauth-provider/src/test/resources/application.yml new file mode 100644 index 0000000..c10ccaa --- /dev/null +++ b/oauth-provider/src/test/resources/application.yml @@ -0,0 +1,22 @@ +# =============================== +# = DATA SOURCE +# =============================== +spring: + datasource: + url: jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driverClassName: org.h2.Driver + username: sa + password: + + jpa: + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + +oauth.client: + admin: + clientId: oauth-provider-admin + secret: default-secret + come2help: + clientId: come2help-web + secret: secret \ No newline at end of file diff --git a/pom.xml b/pom.xml index aeab6f9..d444f0a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,11 +10,13 @@ pom + 1.8 4.12 + 5.2.4.Final - oauth-provider oauth-provider-api + oauth-provider