Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -21,21 +21,35 @@

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse.Indices;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse.ResourcePrivileges;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
import org.elasticsearch.xpack.core.security.authz.ResolvedIndices;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* A custom implementation of an authorization engine. This engine is extremely basic in that it
Expand Down Expand Up @@ -104,6 +118,89 @@ public void loadAuthorizedIndices(RequestInfo requestInfo, AuthorizationInfo aut
}
}

@Override
public void checkPrivileges(Authentication authentication, AuthorizationInfo authorizationInfo,
HasPrivilegesRequest hasPrivilegesRequest,
Collection<ApplicationPrivilegeDescriptor> applicationPrivilegeDescriptors,
ActionListener<HasPrivilegesResponse> listener) {
if (isSuperuser(authentication.getUser())) {
listener.onResponse(getHasPrivilegesResponse(authentication, hasPrivilegesRequest, true));
} else {
listener.onResponse(getHasPrivilegesResponse(authentication, hasPrivilegesRequest, false));
}
}

@Override
public void getUserPrivileges(Authentication authentication, AuthorizationInfo authorizationInfo, GetUserPrivilegesRequest request,
ActionListener<GetUserPrivilegesResponse> listener) {
if (isSuperuser(authentication.getUser())) {
listener.onResponse(getUserPrivilegesResponse(true));
} else {
listener.onResponse(getUserPrivilegesResponse(false));
}
}

private HasPrivilegesResponse getHasPrivilegesResponse(Authentication authentication, HasPrivilegesRequest hasPrivilegesRequest,
boolean authorized) {
Map<String, Boolean> clusterPrivMap = new HashMap<>();
for (String clusterPriv : hasPrivilegesRequest.clusterPrivileges()) {
clusterPrivMap.put(clusterPriv, authorized);
}
final Map<String, ResourcePrivileges> indices = new LinkedHashMap<>();
for (IndicesPrivileges check : hasPrivilegesRequest.indexPrivileges()) {
for (String index : check.getIndices()) {
final Map<String, Boolean> privileges = new HashMap<>();
final HasPrivilegesResponse.ResourcePrivileges existing = indices.get(index);
if (existing != null) {
privileges.putAll(existing.getPrivileges());
}
for (String privilege : check.getPrivileges()) {
privileges.put(privilege, authorized);
}
indices.put(index, new ResourcePrivileges(index, privileges));
}
}
final Map<String, Collection<ResourcePrivileges>> privilegesByApplication = new HashMap<>();
Set<String> applicationNames = Arrays.stream(hasPrivilegesRequest.applicationPrivileges())
.map(RoleDescriptor.ApplicationResourcePrivileges::getApplication)
.collect(Collectors.toSet());
for (String applicationName : applicationNames) {
final Map<String, HasPrivilegesResponse.ResourcePrivileges> appPrivilegesByResource = new LinkedHashMap<>();
for (RoleDescriptor.ApplicationResourcePrivileges p : hasPrivilegesRequest.applicationPrivileges()) {
if (applicationName.equals(p.getApplication())) {
for (String resource : p.getResources()) {
final Map<String, Boolean> privileges = new HashMap<>();
final HasPrivilegesResponse.ResourcePrivileges existing = appPrivilegesByResource.get(resource);
if (existing != null) {
privileges.putAll(existing.getPrivileges());
}
for (String privilege : p.getPrivileges()) {
privileges.put(privilege, authorized);
}
appPrivilegesByResource.put(resource, new HasPrivilegesResponse.ResourcePrivileges(resource, privileges));
}
}
}
privilegesByApplication.put(applicationName, appPrivilegesByResource.values());
}
return new HasPrivilegesResponse(authentication.getUser().principal(), authorized, clusterPrivMap, indices.values(),
privilegesByApplication);
}

private GetUserPrivilegesResponse getUserPrivilegesResponse(boolean isSuperuser) {
final Set<String> cluster = isSuperuser ? Collections.singleton("ALL") : Collections.emptySet();
final Set<ConditionalClusterPrivilege> conditionalCluster = Collections.emptySet();
final Set<GetUserPrivilegesResponse.Indices> indices = isSuperuser ? Collections.singleton(new Indices(Collections.singleton("*"),
Collections.singleton("*"), Collections.emptySet(), Collections.emptySet(), true)) : Collections.emptySet();

final Set<RoleDescriptor.ApplicationResourcePrivileges> application = isSuperuser ?
Collections.singleton(
RoleDescriptor.ApplicationResourcePrivileges.builder().application("*").privileges("*").resources("*").build()) :
Collections.emptySet();
final Set<String> runAs = isSuperuser ? Collections.singleton("*") : Collections.emptySet();
return new GetUserPrivilegesResponse(cluster, conditionalCluster, indices, application, runAs);
}

public static class CustomAuthorizationInfo implements AuthorizationInfo {

private final String[] roles;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -139,6 +145,35 @@ void authorizeIndexAction(RequestInfo requestInfo, AuthorizationInfo authorizati
void loadAuthorizedIndices(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
Map<String, AliasOrIndex> aliasAndIndexLookup, ActionListener<List<String>> listener);

/**
* Checks the current user's privileges against those that being requested to check in the
* request. This provides a way for an application to ask if a user has permission to perform
* an action or if they have permissions to an application resource.
*
* @param authentication the authentication that is associated with this request
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param hasPrivilegesRequest the request that contains the privileges to check for the user
* @param applicationPrivilegeDescriptors a collection of application privilege descriptors
* @param listener the listener to be notified of the has privileges response
*/
void checkPrivileges(Authentication authentication, AuthorizationInfo authorizationInfo, HasPrivilegesRequest hasPrivilegesRequest,
Collection<ApplicationPrivilegeDescriptor> applicationPrivilegeDescriptors,
ActionListener<HasPrivilegesResponse> listener);

/**
* Retrieve's the current user's privileges in a standard format that can be rendered via an
* API for an application to understand the privileges that the current user has.
*
* @param authentication the authentication that is associated with this request
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param request the request for retrieving the user's privileges
* @param listener the listener to be notified of the has privileges response
*/
void getUserPrivileges(Authentication authentication, AuthorizationInfo authorizationInfo, GetUserPrivilegesRequest request,
ActionListener<GetUserPrivilegesResponse> listener);

/**
* Interface for objects that contains the information needed to authorize a request
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,132 +5,47 @@
*/
package org.elasticsearch.xpack.security.action.user;

import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

import static org.elasticsearch.common.Strings.arrayToCommaDelimitedString;
import org.elasticsearch.xpack.security.authz.AuthorizationService;

/**
* Transport action for {@link GetUserPrivilegesAction}
*/
public class TransportGetUserPrivilegesAction extends HandledTransportAction<GetUserPrivilegesRequest, GetUserPrivilegesResponse> {

private final ThreadPool threadPool;
private final CompositeRolesStore rolesStore;
private final AuthorizationService authorizationService;

@Inject
public TransportGetUserPrivilegesAction(ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, CompositeRolesStore rolesStore) {
ActionFilters actionFilters, AuthorizationService authorizationService) {
super(GetUserPrivilegesAction.NAME, transportService, actionFilters, GetUserPrivilegesRequest::new);
this.threadPool = threadPool;
this.rolesStore = rolesStore;
this.authorizationService = authorizationService;
}

@Override
protected void doExecute(Task task, GetUserPrivilegesRequest request, ActionListener<GetUserPrivilegesResponse> listener) {
final String username = request.username();

final User user = Authentication.getAuthentication(threadPool.getThreadContext()).getUser();
final Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext());
final User user = authentication.getUser();
if (user.principal().equals(username) == false) {
listener.onFailure(new IllegalArgumentException("users may only list the privileges of their own account"));
return;
}

// FIXME reuse field permissions cache!
rolesStore.getRoles(user, new FieldPermissionsCache(Settings.EMPTY), ActionListener.wrap(
role -> listener.onResponse(buildResponseObject(role)),
listener::onFailure));
}

// package protected for testing
GetUserPrivilegesResponse buildResponseObject(Role userRole) {
logger.trace(() -> new ParameterizedMessage("List privileges for role [{}]", arrayToCommaDelimitedString(userRole.names())));

// We use sorted sets for Strings because they will typically be small, and having a predictable order allows for simpler testing
final Set<String> cluster = new TreeSet<>();
// But we don't have a meaningful ordering for objects like ConditionalClusterPrivilege, so the tests work with "random" ordering
final Set<ConditionalClusterPrivilege> conditionalCluster = new HashSet<>();
for (Tuple<ClusterPrivilege, ConditionalClusterPrivilege> tup : userRole.cluster().privileges()) {
if (tup.v2() == null) {
if (ClusterPrivilege.NONE.equals(tup.v1()) == false) {
cluster.addAll(tup.v1().name());
}
} else {
conditionalCluster.add(tup.v2());
}
}

final Set<GetUserPrivilegesResponse.Indices> indices = new LinkedHashSet<>();
for (IndicesPermission.Group group : userRole.indices().groups()) {
final Set<BytesReference> queries = group.getQuery() == null ? Collections.emptySet() : group.getQuery();
final Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> fieldSecurity = group.getFieldPermissions().hasFieldLevelSecurity()
? group.getFieldPermissions().getFieldPermissionsDefinition().getFieldGrantExcludeGroups() : Collections.emptySet();
indices.add(new GetUserPrivilegesResponse.Indices(
Arrays.asList(group.indices()),
group.privilege().name(),
fieldSecurity,
queries,
group.allowRestrictedIndices()
));
}

final Set<RoleDescriptor.ApplicationResourcePrivileges> application = new LinkedHashSet<>();
for (String applicationName : userRole.application().getApplicationNames()) {
for (ApplicationPrivilege privilege : userRole.application().getPrivileges(applicationName)) {
final Set<String> resources = userRole.application().getResourcePatterns(privilege);
if (resources.isEmpty()) {
logger.trace("No resources defined in application privilege {}", privilege);
} else {
application.add(RoleDescriptor.ApplicationResourcePrivileges.builder()
.application(applicationName)
.privileges(privilege.name())
.resources(resources)
.build());
}
}
}

final Privilege runAsPrivilege = userRole.runAs().getPrivilege();
final Set<String> runAs;
if (Operations.isEmpty(runAsPrivilege.getAutomaton())) {
runAs = Collections.emptySet();
} else {
runAs = runAsPrivilege.name();
}

return new GetUserPrivilegesResponse(cluster, conditionalCluster, indices, application, runAs);
authorizationService.retrieveUserPrivileges(authentication, request, listener);
}

}
Loading