Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RAS Authentication Provider #195

Merged
merged 31 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d43e4a6
Implement RAS Authentication Provider
Gcolon021 Aug 2, 2024
3952dff
Remove RasAPIEndpoints enum class
Gcolon021 Aug 2, 2024
4dd8cb5
Refactor passport validation and logging
Gcolon021 Aug 2, 2024
9e7da73
Add public dataset access to default user roles
Gcolon021 Aug 8, 2024
fe1af3a
Refactor exception handling and add RAS passport check
Gcolon021 Aug 8, 2024
858688d
Change fetch type of privileges to EAGER
Gcolon021 Aug 8, 2024
c6b365d
Change fetch types for Application and Privilege entities.
Gcolon021 Aug 8, 2024
b22e717
Add `id_token` to authorization code flow test
Gcolon021 Aug 9, 2024
208387c
Refactor `refreshToken` logic and remove redundant tests.
Gcolon021 Aug 9, 2024
9321274
Refactor visa validation logic and update tests
Gcolon021 Aug 9, 2024
f4649bb
Log RAS passport details during authentication
Gcolon021 Aug 9, 2024
18d1a5a
Log RAS passport details during authentication
Gcolon021 Aug 9, 2024
e876285
Add user passport removal on logout
Gcolon021 Aug 9, 2024
e2a9d92
Add RAS passport validation in authentication service
Gcolon021 Aug 13, 2024
1c7d81e
Update pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avil…
Gcolon021 Aug 13, 2024
7a70f92
Update log message for RAS passport
Gcolon021 Aug 13, 2024
b7a0fdf
Verify Issued at (iat) in passport claims
Gcolon021 Aug 13, 2024
d34c01a
Refactor log messages for clarity and add Passport toString
Gcolon021 Aug 14, 2024
f93acb2
Refactor logging statements for clarity and consistency
Gcolon021 Aug 14, 2024
cfdafd6
Enhance login logging in RASAuthenticationService
Gcolon021 Aug 14, 2024
5ea2384
Improve logs for splunk log chaining
Gcolon021 Aug 14, 2024
cb2bd18
Add missing whitespace
Gcolon021 Aug 16, 2024
539fd80
Update passport validation schedule and add logging
Gcolon021 Aug 16, 2024
a523b46
Add check for users with passports before validation
Gcolon021 Aug 16, 2024
226e166
Remove unused property from application.properties
Gcolon021 Aug 16, 2024
83d8953
Remove "fence" keyword from generic functionality
Gcolon021 Aug 19, 2024
7cb3e4c
Refactor role service initialization and clean up RAS PassPortService
Gcolon021 Aug 20, 2024
fca1548
Fix authentication logic in AuthenticationController
Gcolon021 Aug 20, 2024
29b140b
Refactor token refresh response handling.
Gcolon021 Aug 20, 2024
90c0cca
Remove unnecessary comment
Gcolon021 Aug 20, 2024
7dc39e1
Add passport column to user table
Gcolon021 Aug 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ UPDATE access_rule ar
JOIN accessRule_subRule ars ON ars.accessRule_id = ar.uuid
SET ar.subAccessRuleParent_uuid = NULL; -- Remove circular dependency reference so it can be dropped

-- Step 4: Drop the column from the original table
ALTER TABLE access_rule DROP COLUMN subAccessRuleParent_uuid;
-- Step 4: Drop the column from the original table
1 change: 1 addition & 0 deletions pic-sure-auth-db/db/sql/V7__Add_Passport_User_Col.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE user ADD COLUMN passport TEXT;
2 changes: 1 addition & 1 deletion pic-sure-auth-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class})
@EnableJpaRepositories
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class CustomKeyGenerator implements KeyGenerator {
public Object generate(Object target, Method method, Object... params) {
for (Object param : params) {
if (param instanceof User user) {
return user.getEmail();
return user.getSubject();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package edu.harvard.hms.dbmi.avillach.auth.config;

import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.SessionService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService;
import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomLogoutHandler implements LogoutHandler {

private final Logger logger = LoggerFactory.getLogger(CustomLogoutHandler.class);
private final SessionService sessionService;
private final UserService userService;
private final AccessRuleService accessRuleService;
private final JWTUtil jwtUtil;

public CustomLogoutHandler(SessionService sessionService, UserService userService, AccessRuleService accessRuleService, edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil jwtUtil) {
this.sessionService = sessionService;
this.userService = userService;
this.accessRuleService = accessRuleService;
this.jwtUtil = jwtUtil;
}

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String bearer = request.getHeader("Authorization");
String token = bearer.substring(7);
Claims payload = jwtUtil.parseToken(token).getPayload();
String subject = payload.getSubject();

if (StringUtils.isNotBlank(subject)) {
logger.info("logout() Logging out User: {}", subject);
this.sessionService.endSession(subject);
this.userService.evictFromCache(subject);
this.accessRuleService.evictFromCache(subject);
this.userService.removeUserPassport(subject);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package edu.harvard.hms.dbmi.avillach.auth.config;

import edu.harvard.hms.dbmi.avillach.auth.filter.JWTFilter;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.SessionService;
import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService;
import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -20,13 +24,25 @@
public class SecurityConfig {

private final JWTFilter jwtFilter;

private final SessionService sessionService;
private final AuthenticationProvider authenticationProvider;
private final UserService userService;
private final AccessRuleService accessRuleService;
private final JWTUtil jwtUtil;

@Autowired
public SecurityConfig(JWTFilter jwtFilter, AuthenticationProvider authenticationProvider) {
public SecurityConfig(JWTFilter jwtFilter, SessionService sessionService, AuthenticationProvider authenticationProvider, UserService userService, AccessRuleService accessRuleService, JWTUtil jwtUtil) {
this.jwtFilter = jwtFilter;
this.sessionService = sessionService;
this.authenticationProvider = authenticationProvider;
this.userService = userService;
this.accessRuleService = accessRuleService;
this.jwtUtil = jwtUtil;
}

@Bean
public CustomLogoutHandler customLogoutHandler() {
return new CustomLogoutHandler(sessionService, userService, accessRuleService, jwtUtil);
}

@Bean
Expand All @@ -47,8 +63,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
)
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.logout(AbstractHttpConfigurer::disable)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.logout((logout) -> logout.logoutUrl("/logout").addLogoutHandler(customLogoutHandler()));


return http.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public static Map<String, Integer> getTypeNameMap(){
* which means if only part of the gate set is passed, the gate still
* not passed
*/
@ManyToMany(fetch = FetchType.LAZY)
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "accessRule_gate",
joinColumns = {@JoinColumn(name = "accessRule_id", nullable = false)},
inverseJoinColumns = {@JoinColumn(name = "gate_id", nullable = false)})
Expand Down Expand Up @@ -162,7 +162,7 @@ public static Map<String, Integer> getTypeNameMap(){
/**
* introduce sub-accessRule to enable the ability of more complex problem, essentially it is an AND relationship.
*/
@ManyToMany(fetch = FetchType.LAZY)
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "accessRule_subRule",
joinColumns = {@JoinColumn(name = "accessRule_id", nullable = false)},
inverseJoinColumns = {@JoinColumn(name = "subRule_id", nullable = false)})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class Privilege extends BaseEntity {

private String description;

@ManyToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "application_id")
private Application application;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public User(User user) {
@Column(name = "long_term_token")
private String token;

@Column(name = "passport")
private String passport;

public User() {

}
Expand Down Expand Up @@ -280,6 +283,14 @@ public void setToken(String token) {
this.token = token;
}

public String getPassport() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a passport?

return passport;
}

public void setPassport(String passport) {
this.passport = passport;
}

/**
* <p>Inner class defining limited user attributes returned from the User endpoint.</p>
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package edu.harvard.hms.dbmi.avillach.auth.enums;

public enum PassportValidationResponse {
// The Visa’s signature is valid, the Visa has not expired, a valid txn claim is present
VALID("Valid"),
// The encoded Visa’s signature failed validation, is missing or incorrect
INVALID("Invalid"),
// The Passport payload or Visa parameter is missing
MISSING("Missing"),
// The format of the Passport or Visa is incorrect
INVALID_PASSPORT("Invalid Passport"),
// The Visa has expired (as indicated by the “exp” claim)
VISA_EXPIRED("Visa Expired"),
// The Transaction claim (txn) has an error or is missing
TXN_ERROR("Txn Error"),
// The Expiration claim (exp) has an error or is missing
EXPIRATION_ERROR("Expiration Error"),
// A DB related validation error occurred
VALIDATION_ERROR("Validation Error"),
// The Visa was expired because the required polling time was exceeded
EXPIRED_POLLING("Expired Polling"),
// There is a change in the dbGaP permissions associated with the user
PERMISSION_UPDATE("Permission Update");

private final String value;

PassportValidationResponse(String value) {
this.value = value;
}

// FromValue
public static PassportValidationResponse fromValue(String value) {
for (PassportValidationResponse response : PassportValidationResponse.values()) {
if (response.getValue().equals(value)) {
return response;
}
}
return null;
}

public String getValue() {
return value;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package edu.harvard.hms.dbmi.avillach.auth.model;

public record InvalidRefreshToken(String error) implements RefreshToken {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package edu.harvard.hms.dbmi.avillach.auth.model;

public sealed interface RefreshToken permits ValidRefreshToken, InvalidRefreshToken {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package edu.harvard.hms.dbmi.avillach.auth.model;

public record ValidRefreshToken(String token, String expirationDate) implements RefreshToken {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package edu.harvard.hms.dbmi.avillach.auth.model.ras;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class Ga4ghPassportV1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a pain, but do we want to name these what they actually stand for and json map them individually? At the very least we should add documentation what they mean


private String iss;
private String sub;
private long iat;
private long exp;
private String scope;
private String jti;
private String txn;

@JsonProperty("ga4gh_visa_v1")
private Ga4ghVisaV1 ga4ghVisaV1;

@JsonProperty("ras_dbgap_permissions")
private List<RasDbgapPermission> rasDbgagPermissions;

public String getIss() {
return iss;
}

public void setIss(String iss) {
this.iss = iss;
}

public String getSub() {
return sub;
}

public void setSub(String sub) {
this.sub = sub;
}

public long getIat() {
return iat;
}

public void setIat(long iat) {
this.iat = iat;
}

public long getExp() {
return exp;
}

public void setExp(long exp) {
this.exp = exp;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}

public String getJti() {
return jti;
}

public void setJti(String jti) {
this.jti = jti;
}

public String getTxn() {
return txn;
}

public void setTxn(String txn) {
this.txn = txn;
}

public Ga4ghVisaV1 getGa4ghVisaV1() {
return ga4ghVisaV1;
}

public void setGa4ghVisaV1(Ga4ghVisaV1 ga4ghVisaV1) {
this.ga4ghVisaV1 = ga4ghVisaV1;
}

public List<RasDbgapPermission> getRasDbgagPermissions() {
return rasDbgagPermissions;
}

public void setRasDbgagPermissions(List<RasDbgapPermission> rasDbgagPermissions) {
this.rasDbgagPermissions = rasDbgagPermissions;
}

@Override
public String toString() {
return "Ga4ghPassportV1{" +
"iss='" + iss + '\'' +
", sub='" + sub + '\'' +
", iat=" + iat +
", exp=" + exp +
", scope='" + scope + '\'' +
", jti='" + jti + '\'' +
", txn='" + txn + '\'' +
", ga4ghVisaV1=" + ga4ghVisaV1 +
", rasDbgagPermissions=" + rasDbgagPermissions +
'}';
}
}
Loading