Add OpenAPI documentation for the Organization API
Closes #29479 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
parent
8151c93bc7
commit
bfa4660ecd
3 changed files with 74 additions and 27 deletions
|
@ -32,6 +32,10 @@ import jakarta.ws.rs.ext.Provider;
|
|||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
|
@ -41,10 +45,12 @@ import org.keycloak.models.utils.ModelToRepresentation;
|
|||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
|
||||
@Provider
|
||||
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
||||
public class OrganizationIdentityProvidersResource {
|
||||
|
||||
private final RealmModel realm;
|
||||
|
@ -66,6 +72,10 @@ public class OrganizationIdentityProvidersResource {
|
|||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Adds the identity provider with the specified id to the organization",
|
||||
description = "Adds, or associates, an existing identity provider with the organization. If no identity provider is found, " +
|
||||
"or if it is already associated with the organization, an error response is returned")
|
||||
public Response addIdentityProvider(String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
|
@ -90,6 +100,9 @@ public class OrganizationIdentityProvidersResource {
|
|||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Returns all identity providers associated with the organization")
|
||||
public Stream<IdentityProviderRepresentation> getIdentityProviders() {
|
||||
auth.realm().requireManageRealm();
|
||||
return organization.getIdentityProviders().map(this::toRepresentation);
|
||||
|
@ -98,6 +111,11 @@ public class OrganizationIdentityProvidersResource {
|
|||
@Path("{alias}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Returns the identity provider associated with the organization that has the specified alias",
|
||||
description = "Searches for an identity provider with the given alias. If one is found and is associated with the " +
|
||||
"organization, it is returned. Otherwise, an error response with status NOT_FOUND is returned")
|
||||
public IdentityProviderRepresentation getIdentityProvider(@PathParam("alias") String alias) {
|
||||
IdentityProviderModel broker = realm.getIdentityProviderByAlias(alias);
|
||||
|
||||
|
@ -111,6 +129,10 @@ public class OrganizationIdentityProvidersResource {
|
|||
@Path("{alias}")
|
||||
@DELETE
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Removes the identity provider with the specified alias from the organization",
|
||||
description = "Breaks the association between the identity provider and the organization. The provider itself is not deleted. " +
|
||||
"If no provider is found, or if it is not currently associated with the org, an error response is returned")
|
||||
public Response delete(@PathParam("alias") String alias) {
|
||||
IdentityProviderModel broker = realm.getIdentityProviderByAlias(alias);
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package org.keycloak.organization.admin.resource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.ws.rs.Consumes;
|
||||
|
@ -27,7 +25,6 @@ import jakarta.ws.rs.DefaultValue;
|
|||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
|
@ -38,37 +35,28 @@ import jakarta.ws.rs.core.Response.Status;
|
|||
import jakarta.ws.rs.ext.Provider;
|
||||
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import org.keycloak.authentication.actiontoken.inviteorg.InviteOrgActionToken;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailTemplateProvider;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||
import org.keycloak.services.resources.admin.UserResource;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
@Provider
|
||||
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
||||
public class OrganizationMemberResource {
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -98,6 +86,10 @@ public class OrganizationMemberResource {
|
|||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Adds the user with the specified id as a member of the organization", description = "Adds, or associates, " +
|
||||
"an existing user with the organization. If no user is found, or if it is already associated with the organization, " +
|
||||
"an error response is returned")
|
||||
public Response addMember(String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
UserModel user = session.users().getUserById(realm, id);
|
||||
|
@ -119,6 +111,8 @@ public class OrganizationMemberResource {
|
|||
|
||||
@Path("invite-user")
|
||||
@POST
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Invites a new user to the organization using the specified e-mail address")
|
||||
public Response inviteUser(String email) {
|
||||
return new OrganizationInvitationResource(session, organization, adminEvent).inviteUser(email);
|
||||
}
|
||||
|
@ -126,13 +120,17 @@ public class OrganizationMemberResource {
|
|||
@POST
|
||||
@Path("invite-existing-user")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Invites an existing user to the organization, using the specified user id")
|
||||
public Response inviteExistingUser(String id) {
|
||||
return new OrganizationInvitationResource(session, organization, adminEvent).inviteExistingUser(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation( summary = "Return a paginated list of organization members filtered according to the specified parameters")
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation( summary = "Returns a paginated list of organization members filtered according to the specified parameters")
|
||||
public Stream<UserRepresentation> search(
|
||||
@Parameter(description = "A String representing either a member's username, e-mail, first name, or last name.") @QueryParam("search") String search,
|
||||
@Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not") @QueryParam("exact") Boolean exact,
|
||||
|
@ -146,6 +144,11 @@ public class OrganizationMemberResource {
|
|||
@Path("{id}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation( summary = "Returns the member of the organization with the specified id", description = "Searches for a" +
|
||||
"user with the given id. If one is found, and is currently a member of the organization, returns it. Otherwise," +
|
||||
"an error response with status NOT_FOUND is returned")
|
||||
public UserRepresentation get(@PathParam("id") String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (StringUtil.isBlank(id)) {
|
||||
|
@ -157,6 +160,10 @@ public class OrganizationMemberResource {
|
|||
|
||||
@Path("{id}")
|
||||
@DELETE
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Removes the user with the specified id from the organization", description = "Breaks the association " +
|
||||
"between the user and organization. The user itself is not deleted. If no user is found, or if they are not " +
|
||||
"a member of the organization, an error response is returned")
|
||||
public Response delete(@PathParam("id") String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (StringUtil.isBlank(id)) {
|
||||
|
@ -172,17 +179,12 @@ public class OrganizationMemberResource {
|
|||
throw ErrorResponse.error("Not a member of the organization", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Path("{id}")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response update(@PathParam("id") String id, UserRepresentation user) {
|
||||
auth.realm().requireManageRealm();
|
||||
return new UserResource(session, getMember(id), auth, adminEvent).updateUser(user);
|
||||
}
|
||||
|
||||
@Path("{id}/organization")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Returns the organization associated with the user that has the specified id")
|
||||
public OrganizationRepresentation getOrganization(@PathParam("id") String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (StringUtil.isBlank(id)) {
|
||||
|
|
|
@ -39,6 +39,7 @@ import jakarta.ws.rs.core.MediaType;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
|
@ -56,6 +57,7 @@ import org.keycloak.utils.SearchQueryUtils;
|
|||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
@Provider
|
||||
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
|
||||
public class OrganizationResource {
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -75,8 +77,16 @@ public class OrganizationResource {
|
|||
this.adminEvent = adminEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new organization based on the specified {@link OrganizationRepresentation}.
|
||||
*
|
||||
* @param organization the representation containing the organization data.
|
||||
* @return a {@link Response} containing the status of the operation.
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation( summary = "Creates a new organization")
|
||||
public Response create(OrganizationRepresentation organization) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (organization == null) {
|
||||
|
@ -107,7 +117,7 @@ public class OrganizationResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation( summary = "Return a paginated list of organizations filtered according to the specified parameters")
|
||||
@Operation( summary = "Returns a paginated list of organizations filtered according to the specified parameters")
|
||||
public Stream<OrganizationRepresentation> search(
|
||||
@Parameter(description = "A String representing either an organization name or domain") @QueryParam("search") String search,
|
||||
@Parameter(description = "A query to search for custom attributes, in the format 'key1:value2 key2:value2'") @QueryParam("q") String searchQuery,
|
||||
|
@ -126,9 +136,18 @@ public class OrganizationResource {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the organization associated with the specified {@code id}.
|
||||
*
|
||||
* @param id the organization id.
|
||||
* @return the organization associated with the specified id, or {@code null} if no organization is found.
|
||||
*/
|
||||
@Path("{id}")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Returns the organization associated with the specified id, or null if no organization is found")
|
||||
public OrganizationRepresentation get(@PathParam("id") String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (StringUtil.isBlank(id)) {
|
||||
|
@ -140,6 +159,8 @@ public class OrganizationResource {
|
|||
|
||||
@Path("{id}")
|
||||
@DELETE
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Deletes the organization with the specified id")
|
||||
public Response delete(@PathParam("id") String id) {
|
||||
auth.realm().requireManageRealm();
|
||||
if (StringUtil.isBlank(id)) {
|
||||
|
@ -154,6 +175,8 @@ public class OrganizationResource {
|
|||
@Path("{id}")
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
|
||||
@Operation(summary = "Updates the organization with the specified id")
|
||||
public Response update(@PathParam("id") String id, OrganizationRepresentation organization) {
|
||||
auth.realm().requireManageRealm();
|
||||
OrganizationModel model = getOrganization(id);
|
||||
|
|
Loading…
Reference in a new issue