Skip to content

Commit

Permalink
[SYNCOPE-1856] - Added security check to RealmLogic and RealmDetailsT…
Browse files Browse the repository at this point in the history
…abPanel. Edited the style of breadcrumb-item to avoid the double slash after the root realm.
  • Loading branch information
TatoniMatteo committed Jan 22, 2025
1 parent fb91462 commit 3fed08d
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 34 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.SyncopeWebApplication;
import org.apache.syncope.client.console.commons.ITabComponent;
import org.apache.syncope.client.console.layout.AnyLayout;
Expand All @@ -40,12 +42,12 @@
import org.apache.syncope.client.ui.commons.ConnIdSpecialName;
import org.apache.syncope.client.ui.commons.Constants;
import org.apache.syncope.client.ui.commons.status.StatusUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.AnyTypeTO;
import org.apache.syncope.common.lib.to.ConnObject;
import org.apache.syncope.common.lib.to.PropagationStatus;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.ExecStatus;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.wicket.Component;
Expand Down Expand Up @@ -273,47 +275,55 @@ protected RealmDetailsTabPanel() {
@Override
public Panel getPanel(final String panelId) {
ActionsPanel<RealmTO> actionPanel = new ActionsPanel<>("actions", null);
if (securityCheck(Set.of(IdRepoEntitlement.REALM_CREATE, IdRepoEntitlement.REALM_UPDATE,
IdRepoEntitlement.REALM_DELETE))) {
if (securityCheck(IdRepoEntitlement.REALM_CREATE)) {
actionPanel.add(new ActionLink<>(realmTO) {

private static final long serialVersionUID = 2802988981431379827L;

@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickCreate(target);
}
}, ActionLink.ActionType.CREATE, IdRepoEntitlement.REALM_CREATE).hideLabel();
}

if (StringUtils.startsWith(realmTO.getFullPath(), SyncopeConstants.ROOT_REALM)) {
actionPanel.add(new ActionLink<>(realmTO) {

private static final long serialVersionUID = 2802988981431379827L;

@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickCreate(target);
}
}, ActionLink.ActionType.CREATE, IdRepoEntitlement.REALM_CREATE).hideLabel();

actionPanel.add(new ActionLink<>(realmTO) {
if (securityCheck(IdRepoEntitlement.REALM_UPDATE)) {
actionPanel.add(new ActionLink<>(realmTO) {

private static final long serialVersionUID = 2802988981431379828L;
private static final long serialVersionUID = 2802988981431379828L;

@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickEdit(target, realmTO);
}
}, ActionLink.ActionType.EDIT, IdRepoEntitlement.REALM_UPDATE).hideLabel();
@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickEdit(target, realmTO);
}
}, ActionLink.ActionType.EDIT, IdRepoEntitlement.REALM_UPDATE).hideLabel();
}

actionPanel.add(new ActionLink<>(realmTO) {
if (securityCheck(IdRepoEntitlement.REALM_UPDATE)) {
actionPanel.add(new ActionLink<>(realmTO) {

private static final long serialVersionUID = 2802988981431379827L;
private static final long serialVersionUID = 2802988981431379827L;

@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickTemplate(target);
}
}, ActionLink.ActionType.TEMPLATE, IdRepoEntitlement.REALM_UPDATE).hideLabel();
@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickTemplate(target);
}
}, ActionLink.ActionType.TEMPLATE, IdRepoEntitlement.REALM_UPDATE).hideLabel();
}

actionPanel.add(new ActionLink<>(realmTO) {
if (securityCheck(IdRepoEntitlement.REALM_DELETE)) {
actionPanel.add(new ActionLink<>(realmTO) {

private static final long serialVersionUID = 2802988981431379829L;
private static final long serialVersionUID = 2802988981431379829L;

@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickDelete(target, realmTO);
}
}, ActionLink.ActionType.DELETE, IdRepoEntitlement.REALM_DELETE, true).hideLabel();
@Override
public void onClick(final AjaxRequestTarget target, final RealmTO ignore) {
onClickDelete(target, realmTO);
}
}, ActionLink.ActionType.DELETE, IdRepoEntitlement.REALM_DELETE, true).hideLabel();
}
}

RealmDetails panel = new RealmDetails(panelId, realmTO, actionPanel, false);
Expand All @@ -327,5 +337,27 @@ public boolean isVisible() {
return SyncopeWebApplication.get().getSecuritySettings().getAuthorizationStrategy().
isActionAuthorized(this, RENDER);
}

private boolean securityCheck(final String entitlement) {
return securityCheck(Set.of(entitlement));
}

private boolean securityCheck(final Set<String> entitlements) {
UserTO user = SyncopeConsoleSession.get().getSelfTO();
if (user.getUsername().equals("admin")) {
return true;
}
List<String> effectiveRealms = SyncopeConsoleSession.get()
.getSelfTO()
.getRoles()
.stream()
.map(role -> roleRestClient.read(role))
.filter(role -> role.getEntitlements().stream()
.anyMatch(entitlements::contains))
.flatMap(role -> role.getRealms().stream())
.collect(Collectors.toList());

return effectiveRealms.stream().anyMatch(realmTO.getFullPath()::startsWith);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.common.rest.api.beans.RealmQuery;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
Expand Down Expand Up @@ -195,6 +196,15 @@ protected void populateItem(final ListItem<String> item) {

private static final long serialVersionUID = -817438685948164787L;

@Override
protected void onInitialize() {
super.onInitialize();
String fullPath = RealmsUtils.getFullPath(item.getModelObject());
if (!fullPath.equals("/") && fullPath.lastIndexOf("/") == 0) {
item.add(new AttributeModifier("class", "breadcrumb-item no-separator"));
}
}

@Override
public void onClick(final AjaxRequestTarget target) {
realmRestClient.search(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.data.RealmDataBinder;
import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -100,6 +102,14 @@ public RealmLogic(
this.taskExecutor = taskExecutor;
}

protected void securityChecks(final Set<String> effectiveRealms, final String realm) {
boolean authorized = effectiveRealms.stream().anyMatch(realm::startsWith);
if (!authorized) {
throw new DelegatedAdministrationException(realm, User.class.getSimpleName(),
AuthContextUtils.getUsername());
}
}

@PreAuthorize("isAuthenticated()")
@Transactional(readOnly = true)
public Pair<Integer, List<RealmTO>> search(
Expand Down Expand Up @@ -144,6 +154,8 @@ public ProvisioningResult<RealmTO> create(final String parentPath, final RealmTO
}
}

securityChecks(AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_CREATE), parent.getFullPath());

String fullPath = StringUtils.appendIfMissing(parent.getFullPath(), "/") + realmTO.getName();
if (realmDAO.findByFullPath(fullPath) != null) {
throw new DuplicateException(fullPath);
Expand All @@ -168,6 +180,8 @@ public ProvisioningResult<RealmTO> update(final RealmTO realmTO) {
Realm realm = Optional.ofNullable(realmDAO.findByFullPath(realmTO.getFullPath())).
orElseThrow(() -> new NotFoundException(realmTO.getFullPath()));

securityChecks(AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_UPDATE), realm.getFullPath());

Map<Pair<String, String>, Set<Attribute>> beforeAttrs = propagationManager.prepareAttrs(realm);

PropagationByResource<String> propByRes = binder.update(realm, realmTO);
Expand All @@ -191,6 +205,8 @@ public ProvisioningResult<RealmTO> delete(final String fullPath) {
Realm realm = Optional.ofNullable(realmDAO.findByFullPath(fullPath)).
orElseThrow(() -> new NotFoundException(fullPath));

securityChecks(AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_DELETE), realm.getFullPath());

if (!realmDAO.findChildren(realm).isEmpty()) {
throw SyncopeClientException.build(ClientExceptionType.RealmContains);
}
Expand Down

0 comments on commit 3fed08d

Please sign in to comment.