Unverified Commit ce54994b authored by Selvakumar Kamatchinathan's avatar Selvakumar Kamatchinathan Committed by GitHub
Browse files

Merge pull request #9 from selvaebi/T2D267Auth

T2D-267 google OAuth for properties Registry
parents b40f385c 5070db03
# amp-t2d-property-registry
Registry service for AMP T2D properties.
### OAuth2 authenticated rest content server
The authentication is provided by google api.
To perform any valid authenticated request please follow the below steps,
#### What you need are the following:
* Client ID - 548139723323-g70r2bllnlkcgcq85vo5kjshkegitomk.apps.googleusercontent.com
* Client Secret - uYtbvxGubqz5hQGAgGH2P0Qm
#### Requesting Authorization
Now replace the values needed in the following link and put it in a web browser.
``` https://accounts.google.com/o/oauth2/auth?client_id=[Application Client Id]&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=[Scopes]&response_type=code ```
#### Exchanging Authentication code
You should get the standard request for authentication. Once you have accepted authentication copy the Authentication code. Take the following code replace the values as needed.
```
curl \
–request POST \
–data “code=[Authentication code from authorization link]&client_id=[Application Client
Id]&client_secret=[Application Client Secret]&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code” \
https://accounts.google.com/o/oauth2/token
```
You should get something like this:
``` {
“access_token”:“XXXXX”,
“expires_in”:3600,
“id_token”:“XXXXX”,
“refresh_token”:“XXXXX”,
“token_type”:“Bearer”
}
```
You now have an access token you can use in your Google API call.
#### Accessing the endpoints
To access one of the secure rest services we send the access_token like this
```
curl -H "Authorization: Bearer 27647b94-1f9f-4945-ae8f-6521d48fdcad" <host>:<port>/properties/
```
### Sending tokens in swagger
Please pass the token in Authorize box value as "Bearer \<Token>"
\ No newline at end of file
......@@ -23,6 +23,10 @@
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
......@@ -56,6 +60,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
......
......@@ -15,7 +15,7 @@
* limitations under the License.
*
*/
package uk.ac.ebi.ampt2d.registry;
package uk.ac.ebi.ampt2d.registry.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......
......@@ -15,7 +15,7 @@
* limitations under the License.
*
*/
package uk.ac.ebi.ampt2d.registry;
package uk.ac.ebi.ampt2d.registry.config;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Predicate;
......@@ -32,9 +32,13 @@ import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.data.rest.configuration.SpringDataRestConfiguration;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.UiConfiguration;
......@@ -42,6 +46,7 @@ import springfox.documentation.swagger.web.UiConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
......@@ -53,6 +58,12 @@ public class SwaggerConfiguration {
@Autowired
private TypeResolver typeResolver;
private SecurityReference securityReference = SecurityReference.builder()
.reference("Authorization").scopes(new AuthorizationScope[0]).build();
private SecurityContext securityContext = SecurityContext.builder()
.securityReferences(Arrays.asList(securityReference)).build();
@Bean
public Docket propertyRegistryApi() {
return new Docket(DocumentationType.SWAGGER_2)
......@@ -67,15 +78,18 @@ public class SwaggerConfiguration {
new Tag("Phenotype Entity", "Phenotype definition")
)
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(getSubstitutionRules())
;
.securitySchemes(Arrays.asList(new ApiKey("Authorization", "Authorization", "header")))
.securityContexts(Arrays.asList(securityContext))
.alternateTypeRules(getSubstitutionRules());
}
private Predicate<String> getScanRestServicesPathPredicate() {
return Predicates.and(
Predicates.not(PathSelectors.regex("/actuator.*")), // Hide spring-actuator
Predicates.not(PathSelectors.regex("/error.*")), // Hide spring-data error
Predicates.not(PathSelectors.regex("/profile.*")) // Hide spring-data profile
Predicates.not(PathSelectors.regex("/profile.*")),// Hide spring-data profile
Predicates.not(PathSelectors.regex("/users.*")) // Hide user-profile
);
}
......
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry.config.security;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import uk.ac.ebi.ampt2d.registry.entities.User;
import uk.ac.ebi.ampt2d.registry.repositories.UserRepository;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class CustomAuthoritiesExtractor implements AuthoritiesExtractor {
private UserRepository userRepository;
public CustomAuthoritiesExtractor(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
String email = (String) map.get("email");
User user = userRepository.findByEmail(email);
if (user == null) {
user = new User(email, User.Role.ROLE_USER);
userRepository.save(user);
return Arrays.asList(new SimpleGrantedAuthority(User.Role.ROLE_USER.name()));
}
return Arrays.asList(new SimpleGrantedAuthority(user.getRole().toString()));
}
}
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry.config.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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;
@ConditionalOnProperty(value = "security.enabled", havingValue = "false")
@Configuration
@EnableResourceServer
public class DisableSecurityConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll();
}
}
\ No newline at end of file
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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;
import uk.ac.ebi.ampt2d.registry.repositories.UserRepository;
@ConditionalOnProperty(value = "security.enabled", havingValue = "true")
@Configuration
@EnableResourceServer
public class EnableSecurityConfig extends ResourceServerConfigurerAdapter {
private static final String[] AUTH_WHITELIST = {
// -- swagger ui
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**",
"/"
};
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/users/**").hasRole("ADMIN")
.antMatchers(HttpMethod.POST).hasAnyRole("EDITOR", "ADMIN")
.antMatchers(HttpMethod.PUT).hasAnyRole("EDITOR", "ADMIN")
.antMatchers(HttpMethod.PATCH).hasAnyRole("EDITOR", "ADMIN")
.antMatchers(HttpMethod.DELETE).hasAnyRole("EDITOR", "ADMIN")
.anyRequest().authenticated();
}
@Bean
public AuthoritiesExtractor authoritiesExtractor(UserRepository userRepository) {
return new CustomAuthoritiesExtractor(userRepository);
}
}
\ No newline at end of file
......@@ -92,7 +92,7 @@ public class Property {
@JsonProperty
@NotNull
@NotBlank
@Column(nullable = false,columnDefinition = "TEXT")
@Column(nullable = false, columnDefinition = "TEXT")
private String description;
@CreatedDate
......
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry.entities;
import org.hibernate.validator.constraints.Email;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
@Entity
public class User {
public enum Role {
ROLE_USER,
ROLE_EDITOR,
ROLE_ADMIN
}
@Id
@Email
private String email;
@Enumerated(EnumType.STRING)
private Role role;
public User() {
}
public User(String email, Role role) {
this.email = email;
this.role = role;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry.repositories;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import uk.ac.ebi.ampt2d.registry.entities.User;
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, String> {
User findByEmail(@Param("email") String email);
}
......@@ -13,8 +13,12 @@ endpoints.health.sensitive=false
spring.datasource.url=@ampt2d-property-registry-db.url@
spring.datasource.username=@ampt2d-property-registry-db.username@
spring.datasource.password=@ampt2d-property-registry-db.password@
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL94Dialect
spring.jpa.generate-ddl=true
security.enabled=true
security.oauth2.resource.userInfoUri=https://www.googleapis.com/userinfo/v2/me
security.oauth2.resource.filter-order=3
registry.protocols=https
insert into user VALUES ('selva@ebi.ac.uk','ROLE_ADMIN');
insert into user VALUES ('zl@ebi.ac.uk','ROLE_ADMIN');
insert into user VALUES ('iyangar@ebi.ac.uk','ROLE_ADMIN');
\ No newline at end of file
CREATE TABLE IF NOT EXISTS phenotype (
id VARCHAR(255) NOT NULL,
created_date BINARY(255),
last_modified_date BINARY(255),
phenotype_group VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS property (
id VARCHAR(255) NOT NULL,
created_date BINARY(255),
description TEXT NOT NULL,
last_modified_date BINARY(255),
meaning VARCHAR(255) NOT NULL,
type VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS user (
email VARCHAR(255) NOT NULL,
role VARCHAR(255),
PRIMARY KEY (email)
);
\ No newline at end of file
/*
*
* Copyright 2018 EMBL - European Bioinformatics Institute
*
* 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 uk.ac.ebi.ampt2d.registry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import uk.ac.ebi.ampt2d.registry.config.security.CustomAuthoritiesExtractor;
import uk.ac.ebi.ampt2d.registry.repositories.UserRepository;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component
@EnableAuthorizationServer
public class OAuthHelper extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthorizationServerTokenServices tokenservice;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private UserRepository userRepository;
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
private OAuth2AccessToken createAccessToken(final String clientId) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Map<String, Object> clientEmailMap = new HashMap<>();
clientEmailMap.put("email", clientId);
Collection<GrantedAuthority> authorities = authoritiesExtractor(userRepository).extractAuthorities(clientEmailMap);
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("testEditor@gmail.com").and()
.withClient("testUser@gmail.com").and()
.withClient("testAdmin@gmail.com");
}
@Bean
public AuthoritiesExtractor authoritiesExtractor(UserRepository userRepository) {
return new CustomAuthoritiesExtractor(userRepository);
}
}
\ No newline at end of file
......@@ -38,13 +38,16 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@SpringBootTest(properties = "security.enabled=true")
@AutoConfigureMockMvc
public class PropertyRegistryServiceApplicationTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private OAuthHelper oAuthHelper;
@Autowired
private PhenotypeRepository phenotypeRepository;
......@@ -66,7 +69,8 @@ public class PropertyRegistryServiceApplicationTests {
}
private String postTestEntity(String uri, String content) throws Exception {
MvcResult mvcResult = mockMvc.perform(post(uri).content(content))
MvcResult mvcResult = mockMvc.perform(post(uri).with(oAuthHelper.bearerToken("testEditor@gmail.com")).content
(content))
.andExpect(status().isCreated())
.andReturn();
......@@ -90,7 +94,7 @@ public class PropertyRegistryServiceApplicationTests {