Skip to content

Commit

Permalink
Tightening GUI security (authorizations)
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Nov 24, 2017
1 parent d5a039b commit bde9350
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 40 deletions.
Expand Up @@ -38,6 +38,7 @@
import javax.xml.bind.Unmarshaller;
import java.io.InputStream;
import java.util.*;
import java.util.function.Consumer;

/**
* @author lazyman
Expand All @@ -47,6 +48,7 @@ public final class DescriptorLoader implements DebugDumpable {
private static final Trace LOGGER = TraceManager.getTrace(DescriptorLoader.class);

private static Map<String, DisplayableValue<String>[]> actions = new HashMap<>();
private static List<String> permitAllUrls = new ArrayList<>();
private static Map<String, Class> urlClassMap = new HashMap<>();

private String baseFileName = "/descriptor.xml";
Expand Down Expand Up @@ -75,6 +77,10 @@ public String getCustomFileName() {
public void setCustomFileName(String customFileName) {
this.customFileName = customFileName;
}

public static Collection<String> getPermitAllUrls() {
return permitAllUrls;
}

public void loadData(MidPointApplication application) {
LOGGER.debug("Loading data from descriptor files.");
Expand Down Expand Up @@ -135,6 +141,12 @@ private void scanPackagesForPages(List<String> packages, MidPointApplication app
}

private void loadActions(PageDescriptor descriptor) {

if (descriptor.permitAll()) {
foreachUrl(descriptor, url -> permitAllUrls.add(url));
return;
}

List<AuthorizationActionValue> actions = new ArrayList<>();

//avoid of setting guiAll authz for "public" pages (e.g. login page)
Expand All @@ -160,17 +172,21 @@ private void loadActions(PageDescriptor descriptor) {
actions.add(new AuthorizationActionValue(AuthorizationConstants.AUTZ_GUI_ALL_URL,
AuthorizationConstants.AUTZ_GUI_ALL_LABEL, AuthorizationConstants.AUTZ_GUI_ALL_DESCRIPTION));
}

for (String url : descriptor.url()) {
this.actions.put(buildPrefixUrl(url), actions.toArray(new DisplayableValue[actions.size()]));

foreachUrl(descriptor, url -> this.actions.put(url, actions.toArray(new DisplayableValue[actions.size()])));
}

private void foreachUrl(PageDescriptor descriptor, Consumer<String> urlConsumer) {
for (String url : descriptor.url()) {
urlConsumer.accept(buildPrefixUrl(url));
}

for (Url url : descriptor.urls()) {
String urlForSecurity = url.matchUrlForSecurity();
if (StringUtils.isEmpty(urlForSecurity)) {
urlForSecurity = buildPrefixUrl(url.mountUrl());
}
this.actions.put(urlForSecurity, actions.toArray(new DisplayableValue[actions.size()]));
urlConsumer.accept(urlForSecurity);
}
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2014 Evolveum
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,4 +40,9 @@
Class<? extends IPageParametersEncoder> encoder() default PageParametersEncoder.class;

AuthorizationAction[] action() default {};

/**
* Permit access to all users (even non-authenticated users)
*/
boolean permitAll() default false;
}
Expand Up @@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.evolveum.midpoint.web.component.prism.show;

import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
Expand All @@ -24,6 +23,7 @@
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
Expand All @@ -32,6 +32,7 @@
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.AuthorizationAction;
import com.evolveum.midpoint.web.application.PageDescriptor;
import com.evolveum.midpoint.web.component.AjaxButton;
import com.evolveum.midpoint.web.component.breadcrumbs.Breadcrumb;
Expand All @@ -41,6 +42,9 @@
import com.evolveum.midpoint.web.component.wf.ApprovalProcessesPreviewPanel;
import com.evolveum.midpoint.web.page.admin.PageAdmin;
import com.evolveum.midpoint.web.page.admin.PageAdminObjectDetails;
import com.evolveum.midpoint.web.page.admin.roles.PageAdminRoles;
import com.evolveum.midpoint.web.page.admin.services.PageAdminServices;
import com.evolveum.midpoint.web.page.admin.users.PageAdminUsers;
import com.evolveum.midpoint.web.page.admin.workflow.EvaluatedTriggerGroupListPanel;
import com.evolveum.midpoint.web.page.admin.workflow.dto.ApprovalProcessExecutionInformationDto;
import com.evolveum.midpoint.web.page.admin.workflow.dto.EvaluatedTriggerGroupDto;
Expand All @@ -65,9 +69,23 @@
/**
* @author mederly
*/
@PageDescriptor(url = "/admin/previewChanges", encoder = OnePageParameterEncoder.class)
@PageDescriptor(url = "/admin/previewChanges", encoder = OnePageParameterEncoder.class, action = {
@AuthorizationAction(actionUri = PageAdminUsers.AUTH_USERS_ALL,
label = PageAdminUsers.AUTH_USERS_ALL_LABEL,
description = PageAdminUsers.AUTH_USERS_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_USER_URL,
label = "PageUser.auth.user.label",
description = "PageUser.auth.user.description"),
@AuthorizationAction(actionUri = PageAdminRoles.AUTH_ROLE_ALL, label = PageAdminRoles.AUTH_ROLE_ALL_LABEL, description = PageAdminRoles.AUTH_ROLE_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_ROLE_URL, label = "PageRole.auth.role.label", description = "PageRole.auth.role.description"),
@AuthorizationAction(actionUri = PageAdminUsers.AUTH_ORG_ALL, label = PageAdminUsers.AUTH_ORG_ALL_LABEL, description = PageAdminUsers.AUTH_ORG_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_ORG_UNIT_URL, label = "PageOrgUnit.auth.orgUnit.label", description = "PageOrgUnit.auth.orgUnit.description"),
@AuthorizationAction(actionUri = PageAdminServices.AUTH_SERVICES_ALL, label = PageAdminServices.AUTH_SERVICES_ALL_LABEL, description = PageAdminServices.AUTH_SERVICES_ALL_DESCRIPTION),
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_SERVICE_URL, label = "PageService.auth.role.label", description = "PageService.auth.role.description")
})
public class PagePreviewChanges extends PageAdmin {

private static final long serialVersionUID = 1L;

private static final String ID_PRIMARY_DELTAS_SCENE = "primaryDeltas";
private static final String ID_SECONDARY_DELTAS_SCENE = "secondaryDeltas";
private static final String ID_APPROVALS_CONTAINER = "approvalsContainer";
Expand Down
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2010-2017 Evolveum
~
~ 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.
-->

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:extend>

<div class="row">
<div class="col-md-offset-2 col-md-8 col-lg-offset-4 col-lg-4">
<div class="panel panel-default" style="margin-top: 120px;">
<div class="panel-body">
TOP SECRET, burn before reading.
</div>
</div>
</div>
</div>

</wicket:extend>

</body>
</html>
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2017 Evolveum
*
* 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 com.evolveum.midpoint.web.page;

import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.PageDescriptor;
import com.evolveum.midpoint.web.component.form.Form;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.forgetpassword.PageForgotPassword;
import com.evolveum.midpoint.web.security.MidPointApplication;
import com.evolveum.midpoint.web.security.SecurityUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RegistrationsPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType;

import org.apache.commons.lang.StringUtils;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import org.apache.wicket.request.cycle.RequestCycle;
import org.springframework.security.web.WebAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
* Page with no authorizations. It is used for testing: to make sure that nobody can access this page.
*/
@PageDescriptor(url = "/noautz")
public class PageTestNoAuthorizations extends PageBase {
private static final long serialVersionUID = 1L;

private static final Trace LOGGER = TraceManager.getTrace(PageTestNoAuthorizations.class);

public PageTestNoAuthorizations() {
}

@Override
protected void createBreadcrumb() {
}

}
Expand Up @@ -51,7 +51,7 @@
/**
* @author mserbak
*/
@PageDescriptor(url = "/login")
@PageDescriptor(url = "/login", permitAll = true)
public class PageLogin extends PageBase {
private static final long serialVersionUID = 1L;

Expand Down
Expand Up @@ -158,22 +158,28 @@ public void decide(Authentication authentication, Object object, Collection<Conf
throws AccessDeniedException, InsufficientAuthenticationException {

// Too lound, just for testing
LOGGER.trace("decide input: authentication={}, object={}, configAttributes={}",
authentication, object, configAttributes);
// LOGGER.trace("decide input: authentication={}, object={}, configAttributes={}",
// authentication, object, configAttributes);

if (!(object instanceof FilterInvocation)) {
// TODO: is this OK?
LOGGER.trace("DECIDE: PASS because object is not FilterInvocation, it is {}", object);
return;
}

if (configAttributes == null) {
// TODO: is this OK?
LOGGER.trace("DECIDE: PASS because config attributes are null");
FilterInvocation filterInvocation = (FilterInvocation) object;
if (isPermitAll(filterInvocation)) {
LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (permitAll)",
authentication, object);
return;
}

if ("/".equals(filterInvocation.getRequest().getServletPath())) {
// Special case, this is in fact "magic" redirect to home page or login page. It handles autz in its own way.
LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (/)",
authentication, object);
return;
}

FilterInvocation filterInvocation = (FilterInvocation) object;
List<String> requiredActions = new ArrayList<>();

for (PageUrlMapping urlMapping : PageUrlMapping.values()) {
Expand All @@ -185,20 +191,12 @@ public void decide(Authentication authentication, Object object, Collection<Conf
addSecurityConfig(filterInvocation, requiredActions, entry.getKey(), entry.getValue());
}

LOGGER.info("REQ: {}", requiredActions);

if (requiredActions.isEmpty()) {
// TODO: is this OK?
// E.g. login pages go through this. Maybe add permitAll annotation to that page?
LOGGER.trace("DECIDE: PASS because determined empty required actions from {}", filterInvocation);
return;
}

//all users has permission to access these resources
if (requiredActions.contains(AuthorizationConstants.AUTZ_UI_PERMIT_ALL_URL)) {
LOGGER.trace("DECIDE: authentication={}, object={}, requiredActions={}: ALLOW ALL",
authentication, object, requiredActions);
return;
LOGGER.trace("DECIDE: DENY because determined empty required actions from {}", filterInvocation);
SecurityUtil.logSecurityDeny(object, ": Not authorized (page without authorizations)", null, requiredActions);
// Sparse exception method by purpose. We do not want to expose details to attacker.
// Better message is logged.
throw new AccessDeniedException("Not authorized");
}

Object principalObject = authentication.getPrincipal();
Expand Down Expand Up @@ -244,6 +242,16 @@ public void decide(Authentication authentication, Object object, Collection<Conf
}
}

private boolean isPermitAll(FilterInvocation filterInvocation) {
for (String url: DescriptorLoader.getPermitAllUrls()) {
AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
if (matcher.matches(filterInvocation.getRequest())) {
return true;
}
}
return false;
}

private void addSecurityConfig(FilterInvocation filterInvocation, List<String> requiredActions,
String url, DisplayableValue<String>[] actions) {

Expand Down
Expand Up @@ -229,6 +229,7 @@ public void test150CreateWrapperShadow() throws Exception {

PrismObject<ShadowType> shadow = getShadowModel(accountJackOid);
shadow.findReference(ShadowType.F_RESOURCE_REF).getValue().setObject(resourceDummy);
display("Shadow", shadow);

// WHEN
displayWhen(TEST_NAME);
Expand Down

0 comments on commit bde9350

Please sign in to comment.