Skip to content

Commit

Permalink
Merge pull request #947 from terrestris/create-entities-from-provider…
Browse files Browse the repository at this point in the history
…-endpoints

Add endpoints to manually create users, groups and roles from the provider
  • Loading branch information
dnlkoch authored Nov 12, 2024
2 parents a5e35ba + 7dd043e commit e34c9d5
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,22 @@ default void customHttpConfiguration(HttpSecurity http) throws Exception {
"/imagefiles",
"/imagefiles/*"
)
.permitAll()
.permitAll()
// Enable anonymous access to graphql (secured via permission evaluators)
.requestMatchers(
HttpMethod.POST,
"/graphql"
)
.permitAll()
.permitAll()
.requestMatchers(
"/actuator/**",
"/cache/**",
"/webhooks/**",
"/ws/**"
"/ws/**",
// Explicitly require ADMIN role for the provider sync endpoints.
"/users/createAllFromProvider",
"/groups/createAllFromProvider",
"/roles/createAllFromProvider"
)
.hasRole("ADMIN")
.anyRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import de.terrestris.shogun.lib.model.Group;
import de.terrestris.shogun.lib.model.User;
import de.terrestris.shogun.lib.service.GroupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

Expand All @@ -36,8 +40,8 @@
@ConditionalOnExpression("${controller.groups.enabled:true}")
@Log4j2
@Tag(
name = "Users",
description = "The endpoints to manage users"
name = "Groups",
description = "The endpoints to manage groups"
)
@SecurityRequirement(name = "bearer-key")
public class GroupController extends BaseController<GroupService, Group> {
Expand Down Expand Up @@ -93,4 +97,58 @@ public List<User> getGroupMembers(@PathVariable("id") String keycloakId) {
}
}

@PostMapping("/createAllFromProvider")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Creates all groups from the associated group provider (usually Keycloak)",
security = { @SecurityRequirement(name = "bearer-key") }
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "No content: The groups were successfully created"
),
@ApiResponse(
responseCode = "401",
description = "Unauthorized: You need to provide a bearer token"
),
@ApiResponse(
responseCode = "403",
description = "Forbidden: You are not allowed to execute this operation (typically because of a " +
"missing ADMIN role)"
),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something internal went wrong while creating the groups"
)
})
public void createAllFromProvider() {
log.trace("Requested to create all groups from the group provider");

try {
service.createAllFromProvider();

log.trace("Successfully created all groups from the group provider");
} catch (AccessDeniedException ade) {
log.warn("Only users with ROLE_ADMIN are allowed to create groups from the group provider");
log.trace("Full stack trace: ", ade);

throw new ResponseStatusException(
HttpStatus.FORBIDDEN
);
} catch (Exception e) {
log.error("Error while creating the groups from the group provider: \n {}", e.getMessage());
log.trace("Full stack trace: ", e);

throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
messageSource.getMessage(
"BaseController.INTERNAL_SERVER_ERROR",
null,
LocaleContextHolder.getLocale()
),
e
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,85 @@

import de.terrestris.shogun.lib.model.Role;
import de.terrestris.shogun.lib.service.RoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@RestController
@RequestMapping("/roles")
@ConditionalOnExpression("${controller.roles.enabled:true}")
@Log4j2
@Tag(
name = "Roles",
description = "The endpoints to manage roles"
)
@SecurityRequirement(name = "bearer-key")
public class RoleController extends BaseController<RoleService, Role> { }
public class RoleController extends BaseController<RoleService, Role> {

@PostMapping("/createAllFromProvider")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Creates all roles from the associated role provider (usually Keycloak)",
security = { @SecurityRequirement(name = "bearer-key") }
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "No content: The roles were successfully created"
),
@ApiResponse(
responseCode = "401",
description = "Unauthorized: You need to provide a bearer token"
),
@ApiResponse(
responseCode = "403",
description = "Forbidden: You are not allowed to execute this operation (typically because of a " +
"missing ADMIN role)"
),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something internal went wrong while creating the roles"
)
})
public void createAllFromProvider() {
log.trace("Requested to create all roles from the role provider");

try {
service.createAllFromProvider();

log.trace("Successfully created all roles from the role provider");
} catch (AccessDeniedException ade) {
log.warn("Only users with ROLE_ADMIN are allowed to create roles from the role provider");
log.trace("Full stack trace: ", ade);

throw new ResponseStatusException(
HttpStatus.FORBIDDEN
);
} catch (Exception e) {
log.error("Error while creating the roles from the role provider: \n {}", e.getMessage());
log.trace("Full stack trace: ", e);

throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
messageSource.getMessage(
"BaseController.INTERNAL_SERVER_ERROR",
null,
LocaleContextHolder.getLocale()
),
e
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,85 @@

import de.terrestris.shogun.lib.model.User;
import de.terrestris.shogun.lib.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@RestController
@RequestMapping("/users")
@ConditionalOnExpression("${controller.users.enabled:true}")
@Log4j2
@Tag(
name = "Users",
description = "The endpoints to manage users"
)
@SecurityRequirement(name = "bearer-key")
public class UserController extends BaseController<UserService, User> { }
public class UserController extends BaseController<UserService, User> {

@PostMapping("/createAllFromProvider")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(
summary = "Creates all users from the associated user provider (usually Keycloak)",
security = { @SecurityRequirement(name = "bearer-key") }
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "No content: The users were successfully created"
),
@ApiResponse(
responseCode = "401",
description = "Unauthorized: You need to provide a bearer token"
),
@ApiResponse(
responseCode = "403",
description = "Forbidden: You are not allowed to execute this operation (typically because of a " +
"missing ADMIN role)"
),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something internal went wrong while creating the users"
)
})
public void createAllFromProvider() {
log.trace("Requested to create all users from the user provider");

try {
service.createAllFromProvider();

log.trace("Successfully created all users from the user provider");
} catch (AccessDeniedException ade) {
log.warn("Only users with ROLE_ADMIN are allowed to create users from the user provider");
log.trace("Full stack trace: ", ade);

throw new ResponseStatusException(
HttpStatus.FORBIDDEN
);
} catch (Exception e) {
log.error("Error while creating the users from the user provider: \n {}", e.getMessage());
log.trace("Full stack trace: ", e);

throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
messageSource.getMessage(
"BaseController.INTERNAL_SERVER_ERROR",
null,
LocaleContextHolder.getLocale()
),
e
);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public List<Group> findByUser(User user) {
}

/**
* Delete a group from the SHOGun DB by its keycloak Id.
* Delete a group from the SHOGun DB by its keycloak Id.
*
* @param keycloakGroupId
*/
Expand Down Expand Up @@ -137,4 +137,15 @@ public void delete(Group group) {

repository.delete(group);
}

/**
* Reads all groups from the configured group provider (usually Keycloak) and creates them in SHOGun.
*
* Note: Since the group provider service is not secured, this method just wraps it in a secured method and is
* used in the public HTTP REST API.
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void createAllFromProvider() {
groupProviderService.createAllGroups();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,15 @@ public void delete(Role role) {
repository.delete(role);
}

/**
* Reads all roles from the configured group provider (usually Keycloak) and creates them in SHOGun.
*
* Note: Since the role provider service is not secured, this method just wraps it in a secured method and is
* used in the public HTTP REST API.
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void createAllFromProvider() {
roleProviderService.createAllRoles();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,15 @@ public void delete(User user) {
repository.delete(user);
}

/**
* Reads all users from the configured group provider (usually Keycloak) and creates them in SHOGun.
*
* Note: Since the user provider service is not secured, this method just wraps it in a secured method and is
* used in the public HTTP REST API.
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void createAllFromProvider() {
userProviderService.createAllUsers();
}

}
Loading

0 comments on commit e34c9d5

Please sign in to comment.