Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SYNCOPE-1769] Any Objects: unique constraint now on (name, type) #488

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ public AnyObjectCompleteCondition notInGroups(final String group, final String..
notInGroups(group, moreGroups);
}

public AnyObjectCompleteCondition inRelationships(final String anyType, final String... moreAnyTypes) {
public AnyObjectCompleteCondition inRelationships(final String anyObject, final String... moreAnyObjects) {
return newBuilderInstance().
is(SpecialAttr.RELATIONSHIPS.toString()).
inRelationships(anyType, moreAnyTypes);
inRelationships(anyObject, moreAnyObjects);
}

public AnyObjectCompleteCondition notInRelationships(final String anyType, final String... moreAnyTypes) {
public AnyObjectCompleteCondition notInRelationships(final String anyObject, final String... moreAnyObjects) {
return newBuilderInstance().
is(SpecialAttr.RELATIONSHIPS.toString()).
notInRelationships(anyType, moreAnyTypes);
notInRelationships(anyObject, moreAnyObjects);
}

public AnyObjectCompleteCondition inRelationshipTypes(final String type, final String... moreTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ public UserCompleteCondition notInGroups(final String group, final String... mor
notInGroups(group, moreGroups);
}

public UserCompleteCondition inRelationships(final String anyType, final String... moreAnyTypes) {
public UserCompleteCondition inRelationships(final String anyObject, final String... moreAnyObjects) {
return newBuilderInstance().
is(SpecialAttr.RELATIONSHIPS.toString()).
inRelationships(anyType, moreAnyTypes);
inRelationships(anyObject, moreAnyObjects);
}

public UserCompleteCondition notInRelationships(final String anyType, final String... moreAnyTypes) {
public UserCompleteCondition notInRelationships(final String anyObject, final String... moreAnyObjects) {
return newBuilderInstance().
is(SpecialAttr.RELATIONSHIPS.toString()).
notInRelationships(anyType, moreAnyTypes);
notInRelationships(anyObject, moreAnyObjects);
}

public UserCompleteCondition inRelationshipTypes(final String type, final String... moreTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
Expand All @@ -57,15 +59,26 @@ public interface AnyObjectService extends AnyService<AnyObjectTO> {

@ApiResponses(
@ApiResponse(responseCode = "200", description =
"Any object matching the provided key; if value looks like a UUID then it is interpreted as key,"
+ " otherwise as a name.", headers =
"Any object matching the provided key.", headers =
@Header(name = HttpHeaders.ETAG, schema =
@Schema(type = "string"),
description = "Opaque identifier for the latest modification made to the entity returned"
+ " by this endpoint")))
@Override
AnyObjectTO read(String key);

@ApiResponses(
@ApiResponse(responseCode = "200", description =
"Any object matching the provided type and name.", headers =
@Header(name = HttpHeaders.ETAG, schema =
@Schema(type = "string"),
description = "Opaque identifier for the latest modification made to the entity returned"
+ " by this endpoint")))
@GET
@Path("byName/{type}/{name}")
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
AnyObjectTO read(@NotNull @PathParam("type") String type, @NotNull @PathParam("name") String name);

@Override
PagedResult<AnyObjectTO> search(AnyQuery anyQuery);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@
import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
Expand Down Expand Up @@ -223,10 +226,17 @@ protected ConnObject getOnSyncope(
}

protected Any<?> getAny(final Provision provision, final AnyTypeKind anyTypeKind, final String anyKey) {
AnyDAO<Any<?>> dao = anyUtilsFactory.getInstance(anyTypeKind).dao();
Any<?> any = SyncopeConstants.UUID_PATTERN.matcher(anyKey).matches()
? dao.authFind(anyKey)
: dao.authFind(dao.findKey(anyKey));
AnyDAO<?> dao = anyUtilsFactory.getInstance(anyTypeKind).dao();

String actualKey = anyKey;
if (!SyncopeConstants.UUID_PATTERN.matcher(anyKey).matches()) {
actualKey = dao instanceof UserDAO
? ((UserDAO) dao).findKey(anyKey)
: dao instanceof GroupDAO
? ((GroupDAO) dao).findKey(anyKey)
: ((AnyObjectDAO) dao).findKey(provision.getAnyType(), anyKey);
}
Any<?> any = dao.authFind(actualKey);
if (any == null) {
throw new NotFoundException(provision.getAnyType() + " '" + anyKey + "'");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public AnyObjectTO read(final String key) {
return binder.getAnyObjectTO(key);
}

@Transactional(readOnly = true)
public AnyObjectTO read(final String type, final String name) {
return Optional.ofNullable(anyObjectDAO.findKey(type, name)).
map(binder::getAnyObjectTO).
orElseThrow(() -> new NotFoundException("AnyObject " + type + " " + name));
}

@Transactional(readOnly = true)
@Override
public Pair<Integer, List<AnyObjectTO>> search(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public Attr read(final String key, final SchemaType schemaType, final String sch

@Override
public TO read(final String key) {
return getAnyLogic().read(getActualKey(getAnyDAO(), key));
return getAnyLogic().read(findActualKey(getAnyDAO(), key));
}

@Override
Expand Down Expand Up @@ -157,7 +157,7 @@ protected OffsetDateTime findLastChange(final String key) {
}

protected Response doUpdate(final UR updateReq) {
updateReq.setKey(getActualKey(getAnyDAO(), updateReq.getKey()));
updateReq.setKey(findActualKey(getAnyDAO(), updateReq.getKey()));
OffsetDateTime etag = findLastChange(updateReq.getKey());
checkETag(String.valueOf(etag.toInstant().toEpochMilli()));

Expand Down Expand Up @@ -192,23 +192,22 @@ protected void addUpdateOrReplaceAttr(

@Override
public Response update(final String key, final SchemaType schemaType, final Attr attrTO) {
String actualKey = getActualKey(getAnyDAO(), key);
String actualKey = findActualKey(getAnyDAO(), key);
addUpdateOrReplaceAttr(actualKey, schemaType, attrTO, PatchOperation.ADD_REPLACE);
return modificationResponse(read(actualKey, schemaType, attrTO.getSchema()));
}

@Override
public void delete(final String key, final SchemaType schemaType, final String schema) {
addUpdateOrReplaceAttr(
getActualKey(getAnyDAO(), key),
addUpdateOrReplaceAttr(findActualKey(getAnyDAO(), key),
schemaType,
new Attr.Builder(schema).build(),
PatchOperation.DELETE);
}

@Override
public Response delete(final String key) {
String actualKey = getActualKey(getAnyDAO(), key);
String actualKey = findActualKey(getAnyDAO(), key);

OffsetDateTime etag = findLastChange(actualKey);
checkETag(String.valueOf(etag.toInstant().toEpochMilli()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.service.JAXRSService;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -59,7 +61,7 @@ public abstract class AbstractService implements JAXRSService {
@Context
protected SearchContext searchContext;

protected String getActualKey(final AnyDAO<?> dao, final String pretendingKey) {
protected String findActualKey(final AnyDAO<?> dao, final String pretendingKey) {
String actualKey = pretendingKey;
if (uriInfo.getPathParameters(true).containsKey("key")) {
String keyInPath = uriInfo.getPathParameters(true).get("key").get(0);
Expand All @@ -77,7 +79,11 @@ protected String getActualKey(final AnyDAO<?> dao, final String pretendingKey) {
throw sce;
}
if (!SyncopeConstants.UUID_PATTERN.matcher(actualKey).matches()) {
actualKey = dao.findKey(actualKey);
actualKey = dao instanceof UserDAO
? ((UserDAO) dao).findKey(actualKey)
: dao instanceof GroupDAO
? ((GroupDAO) dao).findKey(actualKey)
: null;
}
return actualKey;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ protected AbstractAnyLogic<AnyObjectTO, AnyObjectCR, AnyObjectUR> getAnyLogic()
return logic;
}

@Override
public AnyObjectTO read(final String key) {
return logic.read(key);
}

@Override
public AnyObjectTO read(final String type, final String name) {
return logic.read(type, name);
}

@Override
protected AnyObjectUR newUpdateReq(final String key) {
return new AnyObjectUR.Builder(key).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ public interface AnyDAO<A extends Any<?>> extends DAO<A> {

int DEFAULT_PAGE_SIZE = 500;

String findKey(String name);

List<A> findByKeys(List<String> keys);

OffsetDateTime findLastChange(String key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@

public interface AnyObjectDAO extends AnyDAO<AnyObject> {

String findKey(String type, String name);

AnyObject findByName(String type, String name);

List<AnyObject> findByName(String name);

/**
* Checks if the calling user is authorized to access the Any Object matching the provided key, under the given
* realm.
Expand All @@ -54,8 +60,6 @@ public interface AnyObjectDAO extends AnyDAO<AnyObject> {

Map<String, Integer> countByRealm(AnyType anyType);

AnyObject findByName(String name);

AMembership findMembership(String key);

List<Group> findDynGroups(String key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@

public interface GroupDAO extends AnyDAO<Group> {

String findKey(String name);

Group findByName(String name);

/**
* Checks if the calling user is authorized to access the Group matching the provided key, under the given
* realm.
Expand All @@ -45,8 +49,6 @@ public interface GroupDAO extends AnyDAO<Group> {

Map<String, Integer> countByRealm();

Group findByName(String name);

List<String> findKeysByNamePattern(String pattern);

List<Group> findOwnedByUser(String userKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@

public interface UserDAO extends AnyDAO<User> {

String findKey(String username);

Optional<String> findUsername(String key);

/**
* Checks if the calling user is authorized to access the User matching the provided key, under the given
* realm.
Expand All @@ -46,8 +50,6 @@ public interface UserDAO extends AnyDAO<User> {
*/
void securityChecks(Set<String> authRealms, String key, String realm, Collection<String> groups);

Optional<String> findUsername(String key);

Map<String, Integer> countByRealm();

Map<String, Integer> countByStatus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ protected String getQuery(
final List<Object> parameters,
final SearchSupport svs) {

String memberKey = check(cond);
Set<String> members = check(cond);

StringBuilder query = new StringBuilder().append('(');

Expand All @@ -477,7 +477,9 @@ protected String getQuery(

query.append("SELECT DISTINCT group_id AS any_id FROM ").
append(new SearchSupport(AnyTypeKind.USER).membership().name).append(" WHERE ").
append("any_id=?").append(setParameter(parameters, memberKey)).
append(members.stream().
map(key -> "any_id=?" + setParameter(parameters, key)).
collect(Collectors.joining(" OR "))).
append(") ");

if (not) {
Expand All @@ -488,7 +490,9 @@ protected String getQuery(

query.append("SELECT DISTINCT group_id AS any_id FROM ").
append(new SearchSupport(AnyTypeKind.ANY_OBJECT).membership().name).append(" WHERE ").
append("any_id=?").append(setParameter(parameters, memberKey)).
append(members.stream().
map(key -> "any_id=?" + setParameter(parameters, key)).
collect(Collectors.joining(" OR "))).
append(')');

query.append(')');
Expand Down Expand Up @@ -531,7 +535,7 @@ protected String getQuery(
final List<Object> parameters,
final SearchSupport svs) {

String rightAnyObjectKey = check(cond);
Set<String> rightAnyObjectKeys = check(cond);

StringBuilder query = new StringBuilder().append('(');

Expand All @@ -543,7 +547,9 @@ protected String getQuery(

query.append("SELECT DISTINCT any_id FROM ").
append(svs.relationship().name).append(" WHERE ").
append("right_any_id=?").append(setParameter(parameters, rightAnyObjectKey)).
append(rightAnyObjectKeys.stream().
map(key -> "right_any_id=?" + setParameter(parameters, key)).
collect(Collectors.joining(" OR "))).
append(')');

query.append(')');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import javax.persistence.EntityListeners;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.apache.syncope.core.persistence.api.entity.JSONAttributable;
import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
import org.apache.syncope.core.persistence.api.entity.Membership;
Expand All @@ -36,7 +37,8 @@
import org.apache.syncope.core.persistence.jpa.validation.entity.JPAJSONAttributableCheck;

@Entity
@Table(name = JPAAnyObject.TABLE)
@Table(name = JPAAnyObject.TABLE, uniqueConstraints =
@UniqueConstraint(columnNames = { "name", "type_id" }))
@EntityListeners({ JPAJSONAnyObjectListener.class })
@JPAJSONAttributableCheck
public class JPAJSONAnyObject extends JPAAnyObject implements JSONAttributable<AnyObject>, AnyObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -102,22 +101,6 @@ protected AnyUtils anyUtils() {
return anyUtils;
}

protected String findKey(final String name, final String table) {
Query query = entityManager().createNativeQuery(
"SELECT id FROM " + table + " WHERE " + (JPAUser.TABLE.equals(table) ? "username" : "name") + "=?");
query.setParameter(1, name);

String key = null;

for (Object resultKey : query.getResultList()) {
key = resultKey instanceof Object[]
? (String) ((Object[]) resultKey)[0]
: ((String) resultKey);
}

return key;
}

@SuppressWarnings("unchecked")
protected List<String> findAllKeys(final String table, final int page, final int itemsPerPage) {
Query query = entityManager().createNativeQuery(
Expand Down
Loading