Skip to content

Commit

Permalink
add integration of security service (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtyomyuS committed May 7, 2024
2 parents 6d4a672 + 2d585f0 commit 1d77510
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 1 deletion.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@
<version>${spring_boot_version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>${spring_boot_version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-core -->
<dependency>
<groupId>io.micrometer</groupId>
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/ca/uhn/fhir/jpa/starter/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
import ca.uhn.fhir.rest.server.RestfulServer;
import earth.angelson.security.config.SecurityConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.SpringApplication;
Expand All @@ -37,7 +38,8 @@
WebsocketDispatcherConfig.class,
MdmConfig.class,
JpaBatch2Config.class,
Batch2JobsConfig.class
Batch2JobsConfig.class,
SecurityConfiguration.class
})
public class Application extends SpringBootServletInitializer {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package earth.angelson.security;

import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;


@Interceptor
public class AuthenticationInterceptor {
/**
* This interceptor implements HTTP Basic Auth, which specifies that
* a username and password are provided in a header called Authorization.
*/
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
public boolean incomingRequestPostProcessed(
RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse)
throws AuthenticationException {


String authHeader = theRequest.getHeader("Authorization");

// The format of the header must be:
if (authHeader == null) {
throw new AuthenticationException(Msg.code(642) + "Missing or invalid Authorization header");
} else if ("Bearer dfw98h38r".equals(authHeader)) {
//todo validate token
// This user has access only to Practitioner/1 resources
return true;
} else if ("Bearer 39ff939jgg".equals(authHeader)) {
// This user has access to everything
return true;
} else {
// Throw an HTTP 401
return true;
// throw new AuthenticationException(Msg.code(644) + "Missing or invalid Authorization header value");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package earth.angelson.security;

import earth.angelson.security.cache.TokenCacheService;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;

import java.util.List;

@SuppressWarnings("ConstantConditions")
public class AuthorizationInterceptor extends ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor {

private final TokenCacheService tokenCacheService;

public AuthorizationInterceptor(TokenCacheService tokenCacheService) {
this.tokenCacheService = tokenCacheService;
}

@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
String authHeader = theRequestDetails.getHeader("Authorization");

//todo if service return empty unauthorized request 403
return tokenCacheService.getData(authHeader);
}
}
77 changes: 77 additions & 0 deletions src/main/java/earth/angelson/security/cache/TokenCacheService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package earth.angelson.security.cache;


import earth.angelson.security.dto.RoleAttachmentsDTO;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.List;

public class TokenCacheService {

private final RestTemplate restTemplate;
private final String url;

public TokenCacheService(String url) {
this.restTemplate = new RestTemplate();
this.url = url;
}

@Cacheable(value = "jwtTokenCache", cacheManager = "caffeineCacheManager")
public List<IAuthRule> getData(String token) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);

// Create HttpEntity with headers
HttpEntity<RoleAttachmentsDTO> entity = new HttpEntity<>(headers);

ResponseEntity<RoleAttachmentsDTO> response =
restTemplate.exchange(url, HttpMethod.GET, entity, RoleAttachmentsDTO.class);


if (response.getBody() != null) {
var builder = new RuleBuilder().build();
var role = response.getBody();
role.getRoles().stream().forEach(roleWithRuleDTO -> {
roleWithRuleDTO.getRules().forEach(rule -> {
switch (rule.getOperation()) {
case "READ": {
builder.addAll(new RuleBuilder()
.allow()
.read()
.allResources()
.withAnyId()
.build());
break;
}
case "WRITE": {
builder.addAll(new RuleBuilder()
.allow()
.write()
.allResources()
.withAnyId()
.build());
break;
}
case "ALL": {
builder.addAll(new RuleBuilder().allowAll().build());
break;
}
}

});
});
return builder;
}

// By default, deny everything. This should never get hit, but it's
// good to be defensive
return new RuleBuilder().denyAll().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package earth.angelson.security.config;

import earth.angelson.security.AuthorizationInterceptor;
import earth.angelson.security.cache.TokenCacheService;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class SecurityConfiguration {


@Value("${security.service.url:http:https://localhost:8081/account/info}")
private String securityServiceUrl;

@Bean
public TokenCacheService tokenCacheService() {
return new TokenCacheService(securityServiceUrl);
}

@Bean
public AuthorizationInterceptor authorizationInterceptor(TokenCacheService tokenCacheService) {
return new AuthorizationInterceptor(tokenCacheService);
}

@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager
.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES));
return caffeineCacheManager;
}

}
33 changes: 33 additions & 0 deletions src/main/java/earth/angelson/security/dto/RoleAttachmentsDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package earth.angelson.security.dto;

import java.util.Map;
import java.util.Set;

public class RoleAttachmentsDTO {
private Set<RoleWithRuleDTO> roles;
private Set<Map<String, String>> attachments;

public RoleAttachmentsDTO() {
}

public RoleAttachmentsDTO(Set<RoleWithRuleDTO> roles, Set<Map<String, String>> attachments) {
this.roles = roles;
this.attachments = attachments;
}

public Set<RoleWithRuleDTO> getRoles() {
return roles;
}

public void setRoles(Set<RoleWithRuleDTO> roles) {
this.roles = roles;
}

public Set<Map<String, String>> getAttachments() {
return attachments;
}

public void setAttachments(Set<Map<String, String>> attachments) {
this.attachments = attachments;
}
}
43 changes: 43 additions & 0 deletions src/main/java/earth/angelson/security/dto/RoleWithRuleDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package earth.angelson.security.dto;

import java.util.Set;
import java.util.UUID;

public class RoleWithRuleDTO {
private UUID id;
private String roleName;
private Set<RuleDTO> rules;

public RoleWithRuleDTO() {
}

public RoleWithRuleDTO(UUID id, String roleName, Set<RuleDTO> rules) {
this.id = id;
this.roleName = roleName;
this.rules = rules;
}

public UUID getId() {
return id;
}

public void setId(UUID id) {
this.id = id;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

public Set<RuleDTO> getRules() {
return rules;
}

public void setRules(Set<RuleDTO> rules) {
this.rules = rules;
}
}
66 changes: 66 additions & 0 deletions src/main/java/earth/angelson/security/dto/RuleDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package earth.angelson.security.dto;


import java.util.List;
import java.util.UUID;


public class RuleDTO {

private UUID id;
private String name;
private String operation;
private List<String> resource;
private String filter;

public RuleDTO() {
}

public RuleDTO(UUID id, String name, String operation, List<String> resource, String filter) {
this.id = id;
this.name = name;
this.operation = operation;
this.resource = resource;
this.filter = filter;
}

public UUID getId() {
return id;
}

public void setId(UUID id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getOperation() {
return operation;
}

public void setOperation(String operation) {
this.operation = operation;
}

public List<String> getResource() {
return resource;
}

public void setResource(List<String> resource) {
this.resource = resource;
}

public String getFilter() {
return filter;
}

public void setFilter(String filter) {
this.filter = filter;
}
}

0 comments on commit 1d77510

Please sign in to comment.