Skip to content

Commit

Permalink
Merge pull request #5 from ncats/WIP_auth
Browse files Browse the repository at this point in the history
Wip auth
  • Loading branch information
dkatzel-ncats authored Dec 9, 2020
2 parents d929aa5 + 5d49b6b commit 682595e
Show file tree
Hide file tree
Showing 46 changed files with 1,015 additions and 155 deletions.
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,72 @@ The EntityScan and EnableJpaRepositories need to list all the base packages to s

Please also add your own packages to those lists.

## Security
GSRS uses Spring Secuity for authentication and authorization.


### Authorization
GSRS has built in User Roles for Authorization.
* Query,
* DataEntry,
* SuperDataEntry,
* Updater,
* SuperUpdate,
* Approver,
* Admin

Certain API routes are only allowed to be executed by users who have specific roles.
For example, in order to update a GSRS Entity, you need to have the `Updater` Role.

The GSRS Starter has helper annotations to make this more clear

* `@hasAdminRole`
* `@hasApproverRole`
* `@hasDataEntryRole`
* `@hasSuperDataEntryRole`
* `@hasSuperUpdaterRole`
* `@hasUpdateRole`

You can also use the standard Spring `@PreAuthorize()`/ `@PostAuthorize()` annotations with these roles as well.

It doesn't matter what Authentication mechanism you use as long as your users have these defined Roles.
### Authentication
#### Legacy Authentication
The GSRS Starter supports the legacy GSRS 2 authentication mechanisms such as the GSRS User Profile table and
checking specific headers in REST Requests for authentication information. To Turn this on,
use `@EnableLegacyGsrsAuthentication`.
These are the legacy config options that can be set
```
# SSO HTTP proxy authentication settings
ix.authentication.trustheader=true
ix.authentication.usernameheader="OAM_REMOTE_USER"
ix.authentication.useremailheader="AUTHENTICATION_HEADER_NAME_EMAIL"
# set this "false" to only allow authenticated users to see the application
ix.authentication.allownonauthenticated=true
# set this "true" to allow any user that authenticates to be registered
# as a user automatically
ix.authentication.autoregister=true
#Set this to "true" to allow autoregistered users to be active as well
ix.authentication.autoregisteractive=true
```
##### TrustHeader
This option assumes that the GSRS system is sitting behind a Single Sign On (SSO) System that performs authentication
and will write specific headers to each request as it passes through the SSO gateway.
#### Username and Password in Header
this should only be used in https situations. Legacy GSRS lets you include the GSRS credentials to be put in HTTP headers
as `auth-username` and `auth-password` headers.
## Testing
There is a test module called `gsrs-spring-starter-tests` please add this to your maven pom as a test depdendency
Expand Down
2 changes: 1 addition & 1 deletion gsrs-core-entities/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>gsrs-spring-boot</artifactId>
<groupId>gov.nih.ncats</groupId>
<version>0.0.15-SNAPSHOT</version>
<version>0.0.16-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public abstract class AbstractEntityProcessorFactory implements EntityProcessorF
private final CachedSupplier<Void> initializer = ENTITY_PROCESSOR_FACTORY_INITIALIZER_GROUP.add(CachedSupplier.ofInitializer(()->{
//entityProcessors field may be null if there's no EntityProcessor to inject
processorMapByClass.clear();
cache.clear();
registerEntityProcessor(ep -> {
Class entityClass = ep.getEntityClass();
if (entityClass != null) {
Expand All @@ -42,6 +43,7 @@ public void init(){
*/
protected final void resetCache(){
initializer.resetCache();

}
protected abstract void registerEntityProcessor(Consumer<EntityProcessor> registar);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package gsrs.repository;

import ix.core.models.UserProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {

UserProfile findByUser_Username(String username);

UserProfile findByKey(String key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ public void setPassword(String password) {
this.hashp = Util.encrypt(password, this.salt);
}

public String getEncodePassword(){
return salt.length()+"$"+salt+hashp;
}


public static UserProfile GUEST() {
Expand Down
2 changes: 1 addition & 1 deletion gsrs-core-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>gsrs-spring-boot</artifactId>
<groupId>gov.nih.ncats</groupId>
<version>0.0.15-SNAPSHOT</version>
<version>0.0.16-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion gsrs-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>gsrs-spring-boot</artifactId>
<groupId>gov.nih.ncats</groupId>
<version>0.0.15-SNAPSHOT</version>
<version>0.0.16-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ private synchronized void initIfNeeded(){
}
@Override
public void init(JavaType javaType) {
initIfNeeded();
baseType = javaType;

Class<?> clazz = baseType.getRawClass();
Expand Down
2 changes: 1 addition & 1 deletion gsrs-spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>gsrs-spring-boot</artifactId>
<groupId>gov.nih.ncats</groupId>
<version>0.0.15-SNAPSHOT</version>
<version>0.0.16-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand All @@ -16,5 +17,6 @@
@Target(ElementType.TYPE)
@Import( { GsrsEntitiesConfiguration.class, GsrsJpaEntitySelector.class})
//@EnableJpaRepositories(basePackages = {"ix", "gsrs", "gov.nih.ncats"})
@EnableTransactionManagement(order=1)
public @interface EnableGsrsJpaEntities {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gsrs;

import gsrs.security.GsrsLegacyAuthenticationSelector;
import gsrs.security.LegacyAuthenticationConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import( { LegacyAuthenticationConfiguration.class, GsrsLegacyAuthenticationSelector.class})
public @interface EnableGsrsLegacyAuthentication {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
@Import(value = {AutowireHelper.class, GsrsControllerConfiguration.class,
GsrsApiControllerAdvice.class,
GsrsFactoryConfiguration.class, ConfigBasedGsrsValidatorFactory.class,
JsonTypeIdResolverConfiguration.class, RegisteredFunctionProperties.class})
JsonTypeIdResolverConfiguration.class, RegisteredFunctionProperties.class
})
public class GsrsApiAutoConfiguration {


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import gsrs.service.AbstractGsrsEntityService;
import gsrs.service.GsrsEntityService;
import ix.core.models.Role;
import ix.core.util.EntityUtils;
import ix.core.util.pojopointer.PojoPointer;
import ix.core.validator.ValidationResponse;
Expand All @@ -16,12 +17,14 @@
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.ResourceUrlProvider;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -94,7 +97,11 @@ public GsrsEntityService<T, I> getEntityService() {
// }
// }
@PostGsrsRestApiMapping()
public ResponseEntity<Object> createEntity(@RequestBody JsonNode newEntityJson, @RequestParam Map<String, String> queryParameters) throws IOException {

public ResponseEntity<Object> createEntity(@RequestBody JsonNode newEntityJson,
@RequestParam Map<String, String> queryParameters,
Principal principal) throws IOException {
System.out.println("injected Principal is " + principal);
AbstractGsrsEntityService.CreationResult<T> result = entityService.createEntity(newEntityJson);

if(result.isCreated()){
Expand All @@ -106,6 +113,10 @@ public ResponseEntity<Object> createEntity(@RequestBody JsonNode newEntityJson,

}

protected void requireAnyRoles(Principal principal, Role...roles){
// principal.
}

/*private void foo(){
ValidationResponseBuilder callback = new ValidationUtils.GinasValidationResponseBuilder(objnew, _strategy);
Expand Down Expand Up @@ -147,8 +158,10 @@ public ValidationResponse<T> validateEntity(@RequestBody JsonNode updatedEntityJ
return resp;

}
@PutGsrsRestApiMapping()
public ResponseEntity<Object> updateEntity(@RequestBody JsonNode updatedEntityJson, @RequestParam Map<String, String> queryParameters) throws Exception {
@PutGsrsRestApiMapping("")
public ResponseEntity<Object> updateEntity(@RequestBody JsonNode updatedEntityJson,
@RequestParam Map<String, String> queryParameters,
Principal principal) throws Exception {

AbstractGsrsEntityService.UpdateResult<T> result = entityService.updateEntity(updatedEntityJson);
if(result.getStatus()== AbstractGsrsEntityService.UpdateResult.STATUS.NOT_FOUND){
Expand Down Expand Up @@ -254,7 +267,6 @@ public ResponseEntity<Object> getById(@PathVariable String id, @RequestParam Map
}
return gsrsControllerConfiguration.handleNotFound(queryParameters);
}

@DeleteGsrsRestApiMapping(value = {"/{id}", "({id})"})
public ResponseEntity<Object> deleteById(@PathVariable String id, @RequestParam Map<String, String> queryParameters){
Optional<I> idOptional = entityService.getEntityIdOnlyBySomeIdentifier(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@RequestMapping(
method = {RequestMethod.DELETE}
)
@RequestMapping
@GsrsRestApiRequestMapping
public @interface DeleteGsrsRestApiMapping {
/**
Expand Down Expand Up @@ -82,4 +80,7 @@
@AliasFor(annotation = GsrsRestApiRequestMapping.class)
String[] produces() default {};

@AliasFor(annotation = GsrsRestApiRequestMapping.class)
RequestMethod[] method() default {RequestMethod.DELETE};

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@RequestMapping(
method = {RequestMethod.GET}
)
@RequestMapping
@GsrsRestApiRequestMapping
public @interface GetGsrsRestApiMapping {
/**
Expand Down Expand Up @@ -86,4 +84,7 @@
@AliasFor(annotation = GsrsRestApiRequestMapping.class)
String[] produces() default {};

@AliasFor(annotation = GsrsRestApiRequestMapping.class)
RequestMethod[] method() default {RequestMethod.GET};

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package gsrs.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpServletRequest;

/**
* A Spring Controller Advice that is used to intercept different
* Exceptions thrown by the controller so we can centralize
* how to respond.
*
* This class used the {@link GsrsControllerConfiguration}
* to allow the API to override the error code.
*/
@RestControllerAdvice(assignableTypes = AbstractGsrsEntityController.class)
public class GsrsApiControllerAdvice {
@Autowired
Expand All @@ -19,6 +30,12 @@ public ResponseEntity handleUnchecked(RuntimeException ex, WebRequest request){
return gsrsControllerConfiguration.handleError(ex, request);
}

@ExceptionHandler({AccessDeniedException.class, AuthenticationException.class})
public ResponseEntity<Object> handleAccessDeniedException(Exception ex, WebRequest request) {
return gsrsControllerConfiguration.handleError(HttpStatus.UNAUTHORIZED.value(), ex, request);

}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity handleInvalidJsonInput(HttpMessageNotReadableException ex, WebRequest request){
return gsrsControllerConfiguration.handleError(ex, request);
Expand Down
Loading

0 comments on commit 682595e

Please sign in to comment.