Skip to content

Commit

Permalink
wip: add members to organization
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Igor <[email protected]>
  • Loading branch information
pedroigor committed Mar 15, 2024
1 parent 62d2421 commit 561c01d
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.admin.client.resource;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.representations.idm.UserRepresentation;

public interface OrganizationMemberResource {

@POST
@Consumes(MediaType.APPLICATION_JSON)
Response addMember(UserRepresentation member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
Expand All @@ -38,4 +39,7 @@ public interface OrganizationResource {

@DELETE
Response delete();

@Path("members")
OrganizationMemberResource members();
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class OrganizationEntity {
@Column(name="NAME")
protected String name;

@Column(name = "GROUP_ID")
private String groupId;

public String getId() {
return id;
}
Expand All @@ -69,6 +72,14 @@ public void setName(String name) {
this.name = name;
}

public String getGroupId() {
return groupId;
}

public void setGroupId(String groupId) {
this.groupId = groupId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
Expand All @@ -35,24 +37,38 @@
public class JpaOrganizationProvider implements OrganizationProvider {

private final EntityManager em;
private final GroupProvider groupProvider;
private final KeycloakSession session;

public JpaOrganizationProvider(KeycloakSession session) {
this.session = session;
JpaConnectionProvider jpaProvider = session.getProvider(JpaConnectionProvider.class);
this.em = jpaProvider.getEntityManager();
groupProvider = session.groups();
}

@Override
public OrganizationModel createOrganization(RealmModel realm, String name) {
throwExceptionIfRealmIsNull(realm);

String groupName = "org-" + name;
GroupModel group = groupProvider.getGroupByName(realm, null, name);

if (group != null) {
throw new IllegalArgumentException("A group with the same already exist and it is bound to different organization");
}

OrganizationEntity entity = new OrganizationEntity();

entity.setId(KeycloakModelUtils.generateId());
entity.setRealmId(realm.getId());
entity.setName(name);
group = groupProvider.createGroup(realm, entity.getId(), groupName);
entity.setGroupId(group.getId());

em.persist(entity);

return new OrganizationAdapter(entity);
return new OrganizationAdapter(entity, session);
}

@Override
Expand All @@ -66,6 +82,10 @@ public boolean removeOrganization(RealmModel realm, OrganizationModel organizati
throw new IllegalArgumentException("Organization [" + organization.getId() + " does not belong to realm [" + realm.getId() + "]");
}

GroupModel group = session.groups().getGroupById(realm, toRemove.getGroupId());
session.groups().removeGroup(realm, group);
//TODO: delete users

em.remove(toRemove.getEntity());

return true;
Expand Down Expand Up @@ -111,7 +131,7 @@ private OrganizationAdapter getAdapter(RealmModel realm, String id) {
return null;
}

return new OrganizationAdapter(entity);
return new OrganizationAdapter(entity, session);
}

private void throwExceptionIfOrganizationIsNull(OrganizationModel organization) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@

package org.keycloak.organization.jpa;

import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.JpaModel;
import org.keycloak.models.jpa.entities.OrganizationEntity;

public class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {
public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {

private final OrganizationEntity entity;
private final KeycloakSession session;

public OrganizationAdapter(OrganizationEntity entity) {
public OrganizationAdapter(OrganizationEntity entity, KeycloakSession session) {
this.entity = entity;
this.session = session;
}

@Override
Expand All @@ -38,6 +44,10 @@ String getRealm() {
return entity.getRealmId();
}

String getGroupId() {
return entity.getGroupId();
}

@Override
public void setName(String name) {
entity.setName(name);
Expand All @@ -48,6 +58,20 @@ public String getName() {
return entity.getName();
}

@Override
public boolean addMember(UserModel member) {
RealmModel realm = session.getContext().getRealm();
GroupModel group = session.groups().getGroupById(realm, entity.getGroupId());

if (member.isMemberOf(group)) {
return false;
}

member.joinGroup(group);

return true;
}

@Override
public OrganizationEntity getEntity() {
return entity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@
<column name="REALM_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="GROUP_ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>

<addPrimaryKey columnNames="ID" tableName="ORGANIZATION"/>
<addUniqueConstraint tableName="ORGANIZATION" columnNames="REALM_ID, NAME" constraintName="UK_ORG_NAME"/>
<addUniqueConstraint tableName="ORGANIZATION" columnNames="GROUP_ID" constraintName="UK_ORG_GROUP"/>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ public interface OrganizationModel {

String getName();

boolean addMember(UserModel member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.organization.admin.resource;

import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.Provider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.UsersResource;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;

@Provider
public class OrganizationMemberResource {

private final KeycloakSession session;
private final OrganizationProvider provider;
private final OrganizationModel organization;
private final AdminPermissionEvaluator auth;
private final AdminEventBuilder adminEvent;

public OrganizationMemberResource() {
this.session = null;
this.provider = null;
this.organization = null;
this.auth = null;
this.adminEvent = null;
}

public OrganizationMemberResource(KeycloakSession session, OrganizationModel organization, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.session = session;
this.provider = session == null ? null : session.getProvider(OrganizationProvider.class);
this.organization = organization;
this.auth = auth;
this.adminEvent = adminEvent;
}

@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addMember(UserRepresentation rep) {
UsersResource usersResource = new UsersResource(session, auth, adminEvent);
Response response = usersResource.createUser(rep);

if (Status.CREATED.getStatusCode() == response.getStatus()) {
RealmModel realm = session.getContext().getRealm();
UserModel member = session.users().getUserByUsername(realm, rep.getEmail());

if (organization.addMember(member)) {
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(member.getId()).build()).build();
}
}

throw new BadRequestException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,28 @@
import org.keycloak.models.RealmModel;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.utils.StringUtil;

@Provider
public class OrganizationResource {

private final KeycloakSession session;
private final OrganizationProvider provider;
private final AdminPermissionEvaluator auth;
private final AdminEventBuilder adminEvent;

public OrganizationResource() {
// needed for registering to the JAX-RS stack
this(null);
this(null, null, null);
}

public OrganizationResource(KeycloakSession session) {
public OrganizationResource(KeycloakSession session, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
this.session = session;
this.provider = session == null ? null : session.getProvider(OrganizationProvider.class);
this.auth = auth;
this.adminEvent = adminEvent;
}

@POST
Expand Down Expand Up @@ -110,6 +116,14 @@ public Response update(@PathParam("id") String id, OrganizationRepresentation or
return Response.noContent().build();
}

@Path("{id}/members")
public OrganizationMemberResource members(@PathParam("id") String id) {
RealmModel realm = session.getContext().getRealm();
OrganizationModel model = getOrganization(realm, id);

return new OrganizationMemberResource(session, model, auth, adminEvent);
}

private OrganizationModel getOrganization(RealmModel realm, String id) {
if (id == null) {
throw new BadRequestException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class OrganizationResourceProvider implements AdminRealmResourceProvider

@Override
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
return new OrganizationResource(session);
return new OrganizationResource(session, auth, adminEvent);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ public Response createUser(final UserRepresentation rep) {
RepresentationToModel.createCredentials(rep, session, realm, user, true);
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), user.getId()).representation(rep).success();

if (session.getTransactionManager().isActive()) {
session.getTransactionManager().commit();
}
// if (session.getTransactionManager().isActive()) {
// session.getTransactionManager().commit();
// }

return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(user.getId()).build()).build();
} catch (ModelDuplicateException e) {
Expand Down
Loading

0 comments on commit 561c01d

Please sign in to comment.