diff --git a/articles/RunAs_HOWTO.md b/articles/RunAs_HOWTO.md new file mode 100644 index 000000000..059ed8763 --- /dev/null +++ b/articles/RunAs_HOWTO.md @@ -0,0 +1,187 @@ +# Run-As (Impersonation) — How It Works + +This document describes the Run-As functionality in WebAPI, which allows +an authorized administrator to impersonate another user. + +--- + +## Overview + +Run-As lets an administrator log in as a different user without knowing that +user's credentials. This is useful for: + +- **Troubleshooting** — reproducing issues that a specific user reports by + seeing the application through their permissions. +- **Support** — performing actions on behalf of a user who cannot access the + system themselves. +- **Testing** — verifying that role and permission assignments produce the + correct behavior for a given user. + +When a run-as request succeeds, WebAPI mints a brand-new JWT for the target +user with a fresh session. The administrator effectively becomes that user +for all subsequent requests made with the new token. + +--- + +## Permission + +Run-As is gated by the `admin:run-as` permission. Only users whose roles +include this permission can invoke the endpoint. The permission is seeded by +the baseline migration: + +```sql +INSERT INTO sec_permission (value, description) +VALUES ('admin:run-as', 'Run as another user'); +``` + +Assign this permission to a role, then assign that role to the administrators +who need impersonation capability. + +--- + +## Endpoint + +``` +POST /user/runas +``` + +### Request + +| Parameter | Location | Required | Description | +|-----------|----------|----------|-------------| +| `login` | query string or form body | Yes | The login of the user to impersonate | + +The request must include a valid `Authorization: Bearer ` header for the +calling user (the administrator). + +### Successful Response (200 OK) + +```json +{ + "login": "targetuser", + "jwt": "eyJhbGciOiJIUzI1NiIs...", + "roles": [], + "message": "Run-as successful" +} +``` + +The `jwt` field contains a newly minted token whose `sub` claim is the +target user. The caller should replace their current token with this one. + +### Error Responses + +| Status | Condition | `x-auth-error` Header | +|--------|-----------|-----------------------| +| **403 Forbidden** | Caller does not have the `admin:run-as` permission | — | +| **404 Not Found** | Target user does not exist in the system | `User not found` | + +--- + +## How It Works + +``` +Administrator WebAPI Database + │ │ │ + │ POST /user/runas?login=X │ │ + │────────────────────────────►│ │ + │ │ @PreAuthorize checks │ + │ │ admin:run-as permission │ + │ │ │ + │ │ Look up user "X" │ + │ │──────────────────────────────►│ + │ │◄──────────────────────────────│ + │ │ │ + │ │ Create session for "X" │ + │ │──────────────────────────────►│ + │ │◄──────────────────────────────│ + │ │ │ + │ │ Mint JWT (sub=X, sid=new) │ + │ │ │ + │ { login, jwt, roles, msg } │ │ + │◄────────────────────────────│ │ + │ │ │ + │ (subsequent requests use │ │ + │ the new JWT as user X) │ │ +``` + +### Step by Step + +1. **Authorization check** — Spring Security's `@PreAuthorize` verifies that + the caller has the `admin:run-as` permission before the endpoint method + executes. If not, a 403 is returned immediately. + +2. **Target user lookup** — `AuthorizationService.getUserByLogin()` searches + for the target user. If the user does not exist, a 404 with the + `x-auth-error: User not found` header is returned. + +3. **Session creation** — A new session is created for the target user via + `SessionService.createSession()`. This is a standard session entry in + `sec_session`, identical to one created during normal login. + +4. **JWT minting** — A JWT is generated with `sub` = target login and + `sid` = the new session ID, using the same `JwtService.generateToken()` + used by all other login flows. + +5. **Response** — The JWT is returned in a `LoginService.Result` JSON object. + The Atlas UI replaces its stored token with this new JWT and reloads the + user's permissions via `GET /user/me`. + +--- + +## Returning to Your Own Identity + +Run-As does not maintain a stack or track the original administrator identity +in the token. To return to your own account, simply log out and log back in +with your own credentials. + +--- + +## Nested Run-As + +Nested impersonation is allowed. If the target user also has the `admin:run-as` +permission, they (or the admin acting as them) can invoke `/user/runas` again +to impersonate yet another user. Each call mints a completely independent JWT +and session. + +--- + +## Atlas UI Integration + +The Atlas UI already supports Run-As. When a user with `admin:run-as` +permission is logged in, the welcome screen displays a "Run as" input field +and button. The UI: + +1. Checks the permission via `isPermitted('admin:run-as')`. +2. Sends `POST /user/runas` with the `login` parameter. +3. On success, stores the returned JWT and calls `loadUserInfo()` to refresh + the displayed identity and permissions. +4. On failure, displays the error from the `x-auth-error` response header. + +No UI code changes are required — the endpoint contract matches the existing +Atlas implementation. + +--- + +## Implementation Files + +| File | Role | +|------|------| +| `LoginController.java` | `POST /user/runas` endpoint with `@PreAuthorize` | +| `LoginService.java` | `runAs()` method — user lookup, session creation, JWT minting | +| `AuthorizationService.java` | `getUserByLogin()` facade to the internal `UserService` | + +--- + +## Security Considerations + +- **Audit trail** — All actions performed under a run-as session are attributed + to the target user, not the original administrator. The session ID in the JWT + can be used to correlate actions back to the run-as event in the session + table. +- **Session independence** — The administrator's original session remains valid. + Logging out of the run-as session does not affect the administrator's own + session. +- **Single-login policy** — If `security.session.single-login` is enabled, + creating a run-as session for a target user will revoke any existing sessions + for that user. Be aware of this when impersonating users who are actively + logged in. diff --git a/src/main/java/org/ohdsi/webapi/security/authc/JwtAuthConfig.java b/src/main/java/org/ohdsi/webapi/security/authc/JwtAuthConfig.java index 132a6d3af..1d091f547 100644 --- a/src/main/java/org/ohdsi/webapi/security/authc/JwtAuthConfig.java +++ b/src/main/java/org/ohdsi/webapi/security/authc/JwtAuthConfig.java @@ -22,6 +22,7 @@ import jakarta.annotation.PostConstruct; +import org.ohdsi.webapi.security.authz.User; import org.ohdsi.webapi.security.authz.UserEntity; import org.ohdsi.webapi.security.authz.UserRepository; import org.ohdsi.webapi.security.identity.WebApiPrincipal; @@ -314,7 +315,7 @@ public AbstractAuthenticationToken convert(Jwt jwt) { UserEntity user = userRepository.findByLogin(login).orElseThrow(() -> new BadCredentialsException("User not found: %s".formatted(login))); // Build principal and authentication token - WebApiPrincipal principal = new WebApiPrincipal(user.getId(), user.getLogin()); + WebApiPrincipal principal = new WebApiPrincipal(User.fromEntity(user)); Collection authorities = List.of(); // populate as needed diff --git a/src/main/java/org/ohdsi/webapi/security/authc/LoginController.java b/src/main/java/org/ohdsi/webapi/security/authc/LoginController.java index f506e5579..0b28d104a 100644 --- a/src/main/java/org/ohdsi/webapi/security/authc/LoginController.java +++ b/src/main/java/org/ohdsi/webapi/security/authc/LoginController.java @@ -14,8 +14,11 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import jakarta.servlet.http.HttpServletResponse; + /** * The LoginController class groups the different auth controller endpoints, and * we do it with inner classes @@ -49,7 +52,26 @@ public LoginService.Result refresh(Authentication authentication) { @GetMapping("/user/logout") public LoginService.Result logout(Authentication authentication) { return loginSvc.logout(authentication); - } + } + + /** + * Run-as (impersonation) endpoint. Allows a user with admin:run-as permission + * to log in as another user. + */ + @PostMapping("/user/runas") + @PreAuthorize("isPermitted('admin:run-as')") + public ResponseEntity runAs( + @RequestParam String login, + HttpServletResponse response) { + try { + LoginService.Result result = loginSvc.runAs(login); + return ResponseEntity.ok(result); + } catch (IllegalArgumentException e) { + response.setHeader("x-auth-error", "User not found"); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new LoginService.Result(null, null, null, "User not found")); + } + } /** * Windows Authentication controller which responds with JWT and login results. diff --git a/src/main/java/org/ohdsi/webapi/security/authc/LoginService.java b/src/main/java/org/ohdsi/webapi/security/authc/LoginService.java index 78061f3d2..d345868fb 100644 --- a/src/main/java/org/ohdsi/webapi/security/authc/LoginService.java +++ b/src/main/java/org/ohdsi/webapi/security/authc/LoginService.java @@ -6,6 +6,7 @@ import java.util.UUID; import org.ohdsi.webapi.security.authz.AuthorizationService; +import org.ohdsi.webapi.security.authz.User; import org.ohdsi.webapi.security.session.SessionProperties; import org.ohdsi.webapi.security.session.SessionService; import org.slf4j.Logger; @@ -73,6 +74,29 @@ public Result mintSession(Authentication authentication) { return new Result(login, jwt, roles, "Login successful"); } + /** + * Impersonate another user. Creates a new session for the target user + * and mints a JWT as that user. + * + * @param targetLogin the login of the user to impersonate + * @return Result with JWT for the target user + * @throws IllegalArgumentException if the target user does not exist + */ + public Result runAs(String targetLogin) { + final String login = targetLogin.toLowerCase(); + + User targetUser = authorizationService.getUserByLogin(login) + .orElseThrow(() -> new IllegalArgumentException("User not found: " + login)); + + UUID sessionId = sessionService.createSession(login); + Instant expiresAt = Instant.now().plus(sessionProps.getExpiration()); + String jwt = jwtService.generateToken(login, sessionId.toString(), Date.from(expiresAt)); + + log.info("LoginService: runAs: impersonating {}", login); + + return new Result(login, jwt, new String[]{}, "Run-as successful"); + } + /** * extends the authenticated session by minting a new JWT, and extending the * session in the session store. diff --git a/src/main/java/org/ohdsi/webapi/security/authz/AuthorizationService.java b/src/main/java/org/ohdsi/webapi/security/authz/AuthorizationService.java index 5690a1b4e..e39e8ec67 100644 --- a/src/main/java/org/ohdsi/webapi/security/authz/AuthorizationService.java +++ b/src/main/java/org/ohdsi/webapi/security/authz/AuthorizationService.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.ohdsi.webapi.security.authc.UserOrigin; import org.ohdsi.webapi.security.identity.WebApiPrincipal; @@ -137,9 +138,17 @@ public List filterToExistingRoles(List roleNames) { } public User getCurrentUser() { - WebApiPrincipal principal = getAuthenticatedPrincipal(); - UserEntity ue = this.userService.getUserById(principal.getUserId()); - return User.fromEntity(ue); + return getAuthenticatedPrincipal().getUser(); + } + + /** + * Look up a user by login. + * @param login the login to search for + * @return the User if found, or empty + */ + @Transactional(readOnly = true) + public Optional getUserByLogin(String login) { + return userService.getUserByLogin(login).map(User::fromEntity); } @Transactional(readOnly = true) diff --git a/src/main/java/org/ohdsi/webapi/security/identity/WebApiPrincipal.java b/src/main/java/org/ohdsi/webapi/security/identity/WebApiPrincipal.java index 28a0451b5..6146f83cb 100644 --- a/src/main/java/org/ohdsi/webapi/security/identity/WebApiPrincipal.java +++ b/src/main/java/org/ohdsi/webapi/security/identity/WebApiPrincipal.java @@ -2,33 +2,37 @@ import java.security.Principal; import java.util.Objects; +import org.ohdsi.webapi.security.authz.User; public final class WebApiPrincipal implements Principal { public static final long ANONYMOUS_USER_ID = -1L; public static final String ANONYMOUS_LOGIN = "anonymous"; - public static final WebApiPrincipal ANONYMOUS = new WebApiPrincipal(ANONYMOUS_USER_ID, ANONYMOUS_LOGIN); + public static final WebApiPrincipal ANONYMOUS = + new WebApiPrincipal(new User(ANONYMOUS_USER_ID, ANONYMOUS_LOGIN, "Anonymous")); - private final long userId; - private final String login; + private final User user; - public WebApiPrincipal(long userId, String login) { - this.userId = userId; - this.login = Objects.requireNonNull(login, "login"); + public WebApiPrincipal(User user) { + this.user = Objects.requireNonNull(user, "user"); } public long getUserId() { - return userId; + return user.id(); + } + + public User getUser() { + return user; } @Override public String getName() { - return login; + return user.login(); } public boolean isAnonymous() { - return this == ANONYMOUS || userId == ANONYMOUS_USER_ID; + return this == ANONYMOUS || user.id() == ANONYMOUS_USER_ID; } @Override @@ -38,16 +42,16 @@ public boolean equals(Object o) { if (!(o instanceof WebApiPrincipal)) return false; WebApiPrincipal that = (WebApiPrincipal) o; - return userId == that.userId; + return user.id().equals(that.user.id()); } @Override public int hashCode() { - return Long.hashCode(userId); + return Long.hashCode(user.id()); } @Override public String toString() { - return "WebApiPrincipal[userId=" + userId + ", login=" + login + "]"; + return "WebApiPrincipal[userId=" + user.id() + ", login=" + user.login() + "]"; } } diff --git a/src/main/resources/db/migration/postgresql/B3.0.0__webapi_baseline.sql b/src/main/resources/db/migration/postgresql/B3.0.0__webapi_baseline.sql index fa5715260..deec2fefe 100644 --- a/src/main/resources/db/migration/postgresql/B3.0.0__webapi_baseline.sql +++ b/src/main/resources/db/migration/postgresql/B3.0.0__webapi_baseline.sql @@ -1130,6 +1130,74 @@ CREATE TABLE ${ohdsiSchema}.sec_source -- END sec_{entity} tables +-- FK constraints for sec_{entity} tables: role and entity references. +-- Both FKs use ON DELETE CASCADE: deleting a role or a parent entity automatically +-- removes its permission grants in the corresponding sec_* table. + +ALTER TABLE ${ohdsiSchema}.sec_cohort_characterization + ADD CONSTRAINT fk_scc_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scc_cohort_characterization_id + FOREIGN KEY (cohort_characterization_id) REFERENCES ${ohdsiSchema}.cohort_characterization(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_cohort_definition + ADD CONSTRAINT fk_scd_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scd_cohort_definition_id + FOREIGN KEY (cohort_definition_id) REFERENCES ${ohdsiSchema}.cohort_definition(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_concept_set + ADD CONSTRAINT fk_scs_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scs_concept_set_id + FOREIGN KEY (concept_set_id) REFERENCES ${ohdsiSchema}.concept_set(concept_set_id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_fe_analysis + ADD CONSTRAINT fk_sfa_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sfa_fe_analysis_id + FOREIGN KEY (fe_analysis_id) REFERENCES ${ohdsiSchema}.fe_analysis(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_ir_analysis + ADD CONSTRAINT fk_sia_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sia_ir_analysis_id + FOREIGN KEY (ir_id) REFERENCES ${ohdsiSchema}.ir_analysis(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_pathway_analysis + ADD CONSTRAINT fk_spa_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_spa_pathway_analysis_id + FOREIGN KEY (pathway_analysis_id) REFERENCES ${ohdsiSchema}.pathway_analysis(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_reusable + ADD CONSTRAINT fk_sr_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sr_reusable_id + FOREIGN KEY (reusable_id) REFERENCES ${ohdsiSchema}.reusable(id) + ON DELETE CASCADE; + +ALTER TABLE ${ohdsiSchema}.sec_source + ADD CONSTRAINT fk_ss_sec_role_id + FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_ss_source_id + FOREIGN KEY (source_id) REFERENCES ${ohdsiSchema}.source(source_id) + ON DELETE CASCADE; + CREATE TABLE ${ohdsiSchema}.sec_session ( session_id uuid NOT NULL, @@ -1707,48 +1775,6 @@ ALTER TABLE ONLY ${ohdsiSchema}.reusable_version ALTER TABLE ONLY ${ohdsiSchema}.reusable_version ADD CONSTRAINT fk_reusable_version_sec_user_creator FOREIGN KEY (created_by_id) REFERENCES ${ohdsiSchema}.sec_user(id); -ALTER TABLE ONLY ${ohdsiSchema}.sec_cohort_characterization - ADD CONSTRAINT fk_scc_cohort_characterization_id FOREIGN KEY (cohort_characterization_id) REFERENCES ${ohdsiSchema}.cohort_characterization (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_cohort_characterization - ADD CONSTRAINT fk_scc_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_cohort_definition - ADD CONSTRAINT fk_scd_cohort_definition_id FOREIGN KEY (cohort_definition_id) REFERENCES ${ohdsiSchema}.cohort_definition (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_cohort_definition - ADD CONSTRAINT fk_scd_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_concept_set - ADD CONSTRAINT fk_scs_concept_set_id FOREIGN KEY (concept_set_id) REFERENCES ${ohdsiSchema}.concept_set (concept_set_id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_concept_set - ADD CONSTRAINT fk_scs_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_fe_analysis - ADD CONSTRAINT fk_sfa_fe_analysis_id FOREIGN KEY (fe_analysis_id) REFERENCES ${ohdsiSchema}.fe_analysis (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_fe_analysis - ADD CONSTRAINT fk_sfa_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_ir_analysis - ADD CONSTRAINT fk_sia_ir_analysis_id FOREIGN KEY (ir_id) REFERENCES ${ohdsiSchema}.ir_analysis (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_ir_analysis - ADD CONSTRAINT fk_sia_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_pathway_analysis - ADD CONSTRAINT fk_spa_pathway_analysis_id FOREIGN KEY (pathway_analysis_id) REFERENCES ${ohdsiSchema}.pathway_analysis (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_pathway_analysis - ADD CONSTRAINT fk_spa_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_reusable - ADD CONSTRAINT fk_sr_reusable_id FOREIGN KEY (reusable_id) REFERENCES ${ohdsiSchema}.reusable (id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_reusable - ADD CONSTRAINT fk_sr_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role (id); - ALTER TABLE ONLY ${ohdsiSchema}.sec_role_group ADD CONSTRAINT fk_role_group_job FOREIGN KEY (job_id) REFERENCES ${ohdsiSchema}.user_import_job(id) ON DELETE CASCADE; @@ -1770,12 +1796,6 @@ ALTER TABLE ONLY ${ohdsiSchema}.source ALTER TABLE ONLY ${ohdsiSchema}.source ADD CONSTRAINT source_modified_by_id_fkey FOREIGN KEY (modified_by_id) REFERENCES ${ohdsiSchema}.sec_user(id); -ALTER TABLE ONLY ${ohdsiSchema}.sec_source - ADD CONSTRAINT fk_ss_source_id FOREIGN KEY (source_id) REFERENCES ${ohdsiSchema}.source(source_id); - -ALTER TABLE ONLY ${ohdsiSchema}.sec_source - ADD CONSTRAINT fk_ss_sec_role_id FOREIGN KEY (role_id) REFERENCES ${ohdsiSchema}.sec_role(id); - ALTER TABLE ONLY ${ohdsiSchema}.source_daimon ADD CONSTRAINT fk_source_daimon_source_id FOREIGN KEY (source_id) REFERENCES ${ohdsiSchema}.source(source_id); diff --git a/src/main/resources/db/migration/postgresql/V2.99.0004__cascade_delete_sec.sql b/src/main/resources/db/migration/postgresql/V2.99.0004__cascade_delete_sec.sql new file mode 100644 index 000000000..b160273b8 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.99.0004__cascade_delete_sec.sql @@ -0,0 +1,114 @@ +-- Add ON DELETE CASCADE to both entity and role FK constraints on all sec_* access tables. +-- Deleting a parent entity or a role will automatically remove its permission grants. + +-- sec_cohort_definition +ALTER TABLE ${ohdsiSchema}.sec_cohort_definition + DROP CONSTRAINT fk_scd_cohort_definition_id, + DROP CONSTRAINT fk_scd_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_cohort_definition + ADD CONSTRAINT fk_scd_cohort_definition_id + FOREIGN KEY (cohort_definition_id) + REFERENCES ${ohdsiSchema}.cohort_definition(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scd_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_concept_set +ALTER TABLE ${ohdsiSchema}.sec_concept_set + DROP CONSTRAINT fk_scs_concept_set_id, + DROP CONSTRAINT fk_scs_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_concept_set + ADD CONSTRAINT fk_scs_concept_set_id + FOREIGN KEY (concept_set_id) + REFERENCES ${ohdsiSchema}.concept_set(concept_set_id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scs_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_cohort_characterization +ALTER TABLE ${ohdsiSchema}.sec_cohort_characterization + DROP CONSTRAINT fk_scc_cohort_characterization_id, + DROP CONSTRAINT fk_scc_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_cohort_characterization + ADD CONSTRAINT fk_scc_cohort_characterization_id + FOREIGN KEY (cohort_characterization_id) + REFERENCES ${ohdsiSchema}.cohort_characterization(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_scc_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_ir_analysis +ALTER TABLE ${ohdsiSchema}.sec_ir_analysis + DROP CONSTRAINT fk_sia_ir_analysis_id, + DROP CONSTRAINT fk_sia_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_ir_analysis + ADD CONSTRAINT fk_sia_ir_analysis_id + FOREIGN KEY (ir_id) + REFERENCES ${ohdsiSchema}.ir_analysis(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sia_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_fe_analysis +ALTER TABLE ${ohdsiSchema}.sec_fe_analysis + DROP CONSTRAINT fk_sfa_fe_analysis_id, + DROP CONSTRAINT fk_sfa_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_fe_analysis + ADD CONSTRAINT fk_sfa_fe_analysis_id + FOREIGN KEY (fe_analysis_id) + REFERENCES ${ohdsiSchema}.fe_analysis(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sfa_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_pathway_analysis +ALTER TABLE ${ohdsiSchema}.sec_pathway_analysis + DROP CONSTRAINT fk_spa_pathway_analysis_id, + DROP CONSTRAINT fk_spa_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_pathway_analysis + ADD CONSTRAINT fk_spa_pathway_analysis_id + FOREIGN KEY (pathway_analysis_id) + REFERENCES ${ohdsiSchema}.pathway_analysis(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_spa_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_reusable +ALTER TABLE ${ohdsiSchema}.sec_reusable + DROP CONSTRAINT fk_sr_reusable_id, + DROP CONSTRAINT fk_sr_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_reusable + ADD CONSTRAINT fk_sr_reusable_id + FOREIGN KEY (reusable_id) + REFERENCES ${ohdsiSchema}.reusable(id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_sr_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; + +-- sec_source +ALTER TABLE ${ohdsiSchema}.sec_source + DROP CONSTRAINT fk_ss_source_id, + DROP CONSTRAINT fk_ss_sec_role_id; +ALTER TABLE ${ohdsiSchema}.sec_source + ADD CONSTRAINT fk_ss_source_id + FOREIGN KEY (source_id) + REFERENCES ${ohdsiSchema}.source(source_id) + ON DELETE CASCADE, + ADD CONSTRAINT fk_ss_sec_role_id + FOREIGN KEY (role_id) + REFERENCES ${ohdsiSchema}.sec_role(id) + ON DELETE CASCADE; diff --git a/src/test/java/org/ohdsi/webapi/security/PermissionTest.java b/src/test/java/org/ohdsi/webapi/security/PermissionTest.java index d1ea29f95..87abe5494 100644 --- a/src/test/java/org/ohdsi/webapi/security/PermissionTest.java +++ b/src/test/java/org/ohdsi/webapi/security/PermissionTest.java @@ -29,6 +29,7 @@ import org.ohdsi.webapi.AbstractDatabaseTest; import org.ohdsi.webapi.security.authc.WebApiAuthenticationToken; import org.ohdsi.webapi.security.authz.AuthorizationService; +import org.ohdsi.webapi.security.authz.User; import org.ohdsi.webapi.security.identity.WebApiPrincipal; import org.ohdsi.webapi.util.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -49,7 +50,7 @@ public class PermissionTest extends AbstractDatabaseTest { @Before public void setup() { // Set the Principal for the current thread - WebApiPrincipal principal = new WebApiPrincipal(100001, "permsTest"); + WebApiPrincipal principal = new WebApiPrincipal(new User(100001L, "permsTest", "Permission Test")); Authentication auth = WebApiAuthenticationToken.authenticated(principal, UUID.randomUUID(), Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); } diff --git a/src/test/java/org/ohdsi/webapi/test/ITStarter.java b/src/test/java/org/ohdsi/webapi/test/ITStarter.java index 002c1a276..87eacfb75 100644 --- a/src/test/java/org/ohdsi/webapi/test/ITStarter.java +++ b/src/test/java/org/ohdsi/webapi/test/ITStarter.java @@ -5,6 +5,7 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Suite; +import org.ohdsi.webapi.security.authz.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.test.context.TestPropertySource; @@ -44,7 +45,7 @@ public static void before() throws IOException { } // set up Spring Security test principal (replaces legacy Shiro subject) - org.ohdsi.webapi.security.identity.WebApiPrincipal principal = new org.ohdsi.webapi.security.identity.WebApiPrincipal(1L, "admin@odysseusinc.com"); + org.ohdsi.webapi.security.identity.WebApiPrincipal principal = new org.ohdsi.webapi.security.identity.WebApiPrincipal(new User(1L, "admin@odysseusinc.com", "Admin")); setSubject(principal); } }