Skip to content

Commit

Permalink
Add endpoint to fetch the roles of an authenticated user
Browse files Browse the repository at this point in the history
  • Loading branch information
barreiro committed Feb 5, 2024
1 parent 36bc220 commit 2130765
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "user", description = "Manage users")
public interface UserService {

@GET
@Path("roles")
@Blocking
List<String> getRoles();

@GET
@Path("search")
@Blocking
Expand Down
4 changes: 4 additions & 0 deletions horreum-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elasticsearch-rest-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ private static UserData toUserInfo(UserRepresentation rep) {
return new UserData(rep.getId(), rep.getUsername(), rep.getFirstName(), rep.getLastName(), rep.getEmail());
}

@Override public List<String> getRoles() {
if (identity.isAnonymous()) {
throw ServiceException.forbidden("Please log in and try again");
}
var representations = keycloak.realm(realm).users().search(identity.getPrincipal().getName(), true);
if (representations.isEmpty()) {
throw ServiceException.notFound("Username not found");
}
var roles = keycloak.realm(realm).users().get(representations.get(0).getId()).roles().getAll().getRealmMappings();
return roles.stream().map(RoleRepresentation::getName).collect(Collectors.toList());
}

@Override
public List<UserData> searchUsers(String query) {
if (identity.isAnonymous()) {
Expand Down
7 changes: 7 additions & 0 deletions horreum-backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,10 @@ horreum.dev-services.enabled=true
quarkus.datasource.devservices.enabled=false
quarkus.datasource.migration.devservices.enabled=false
quarkus.keycloak.devservices.enabled=false

## Add a dummy administrator in dev mode with name "user" and password "secret" with Basic HTTP authentication
%dev.quarkus.http.auth.basic=true
%dev.quarkus.security.users.embedded.enabled=true
%dev.quarkus.security.users.embedded.plain-text=true
%dev.quarkus.security.users.embedded.users.user=secret
%dev.quarkus.security.users.embedded.roles.user=admin
20 changes: 13 additions & 7 deletions horreum-web/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {AppContextType} from "./context/@types/appContextTypes";
import {Button, Form, FormGroup, Modal, Spinner, TextInput} from "@patternfly/react-core";
import {userApi} from "./api";
import store from "./store";
import {BASIC_AUTH, UPDATE_ROLES, AFTER_LOGOUT} from "./auth";
import {BASIC_AUTH, UPDATE_ROLES, AFTER_LOGOUT, STORE_PROFILE, UPDATE_DEFAULT_TEAM} from "./auth";

type LoginModalProps = {
username: string
Expand Down Expand Up @@ -38,12 +38,18 @@ export default function LoginModal(props: LoginModalProps) {
onClick={() => {
setCreating(true)
store.dispatch({type: BASIC_AUTH, username, password});

// TODO: instead of fetching userdata, should be fetching the user roles instead
userApi.info([username || ''])
.then(userdata => {
alerting.dispatchInfo("LOGIN", "Log in successful", "Successful log in of user " + userdata[0].username, 3000)
store.dispatch({type: UPDATE_ROLES, authenticated: true, roles: [/* TODO: the roles fetched */]});

userApi.getRoles()
.then(roles => {
alerting.dispatchInfo("LOGIN", "Log in successful", "Successful log in of user " + username, 3000)
store.dispatch({type: UPDATE_ROLES, authenticated: true, roles: roles});
userApi.searchUsers(username!).then(
userData => store.dispatch({type: STORE_PROFILE, profile: userData.filter(u => u.username == username).at(0)}),
error => alerting.dispatchInfo("LOGIN", "Unable to get user profile", error, 30000))
userApi.defaultTeam().then(
response => store.dispatch({ type: UPDATE_DEFAULT_TEAM, team: response }),
error => alerting.dispatchInfo("LOGIN", "Cannot retrieve default team", error, 30000)
)
},
error => {
alerting.dispatchInfo("LOGIN", "Failed to authenticate", error, 30000)
Expand Down
1 change: 1 addition & 0 deletions horreum-web/src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const authMiddleware: Middleware = {
url: ctx.url,
init: {
...ctx.init,
credentials: "omit", // this prevents the browser from showing the native auth dialog
headers: {...ctx.init.headers, Authorization: "Basic " + basicAuthToken},
},
})
Expand Down
4 changes: 2 additions & 2 deletions horreum-web/src/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class AuthState {

interface InitAction {
type: typeof INIT
keycloak: Keycloak
keycloak?: Keycloak
initPromise?: Promise<boolean>
}

Expand All @@ -46,7 +46,7 @@ interface UpdateRolesAction {

interface StoreProfileAction {
type: typeof STORE_PROFILE
profile: KeycloakProfile
profile?: KeycloakProfile
}

interface BasicAuthAction {
Expand Down
2 changes: 1 addition & 1 deletion horreum-web/src/keycloak.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function initKeycloak(state: State) {
}
})
}
store.dispatch({ type: INIT, keycloak: keycloak, initPromise: initPromise })
store.dispatch({ type: INIT, keycloak: oidc ? keycloak : undefined, initPromise: initPromise })
})
.catch(noop)
}

0 comments on commit 2130765

Please sign in to comment.