Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/development/extensions-core/druid-pac4j.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ druid.auth.authenticator.jwt.type=jwt
|`druid.auth.pac4j.oidc.discoveryURI`|discovery URI for fetching OP metadata [see this](http://openid.net/specs/openid-connect-discovery-1_0.html).|none|Yes|
|`druid.auth.pac4j.oidc.oidcClaim`|[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) that will be extracted from the ID Token after validation.|name|No|
|`druid.auth.pac4j.oidc.scope`| scope is used by an application during authentication to authorize access to a user's details.|`openid profile email`|No|
|`druid.auth.pac4j.oidc.roleClaimPath`| Dot-separated path to the claim containing user roles|none|No|

:::info
Users must set a strong passphrase to ensure that an attacker is not able to guess it simply by brute force.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ public Set<String> getRoles(String authorizerPrefix, AuthenticationResult authen
}
}

// Get the roles assigned to LDAP user from the metastore.
// This allow us to authorize LDAP users regardless of whether they belong to any groups or not in LDAP.
BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
if (user != null) {
roleNames.addAll(user.getRoles());
}
roleNames.addAll(RoleProviderUtil.getUserRoles(
userMap,
authorizerPrefix,
authenticationResult,
cacheManager
));

return roleNames;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ public MetadataStoreRoleProvider(
@Override
public Set<String> getRoles(String authorizerPrefix, AuthenticationResult authenticationResult)
{
Set<String> roleNames = new HashSet<>();

Map<String, BasicAuthorizerUser> userMap = cacheManager.getUserMap(authorizerPrefix);
if (userMap == null) {
throw new IAE("Could not load userMap for authorizer [%s]", authorizerPrefix);
}

BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
if (user != null) {
roleNames.addAll(user.getRoles());
}
return roleNames;
return new HashSet<>(RoleProviderUtil.getUserRoles(
userMap,
authorizerPrefix,
authenticationResult,
cacheManager
));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.druid.security.basic.authorization;

import com.google.common.annotations.VisibleForTesting;
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
import org.apache.druid.server.security.AuthenticationResult;

import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class RoleProviderUtil
{
public static final String ROLE_CLAIM_CONTEXT_KEY = "druidRoles";

public static Set<String> getUserRoles(
Map<String, BasicAuthorizerUser> userMap,
String authorizerPrefix,
AuthenticationResult authenticationResult,
BasicAuthorizerCacheManager cacheManager
)
{
Set<String> claims = RoleProviderUtil.claimValuesFromCtx(authenticationResult.getContext());

if (claims != null) {
return getRolesByClaimValue(
authorizerPrefix,
claims,
cacheManager
);
} else {
return getRolesByIdentity(
userMap,
authenticationResult.getIdentity()
);
}
}

@VisibleForTesting
public static Set<String> getRolesByIdentity(
Map<String, BasicAuthorizerUser> userMap,
String identity
)
{
Set<String> roles = new HashSet<>();

BasicAuthorizerUser user = userMap.get(identity);
if (user != null) {
roles.addAll(user.getRoles());
}
return roles;
}

@VisibleForTesting
public static Set<String> getRolesByClaimValue(
String authorizerPrefix,
Set<String> claimValue,
BasicAuthorizerCacheManager cacheManager
)
{
Map<String, BasicAuthorizerRole> roleMap = cacheManager.getRoleMap(authorizerPrefix);

if (roleMap == null) {
return Set.of();
}

Set<String> roles = new HashSet<>();

roleMap.keySet()
.stream()
.filter(claimValue::contains)
.forEach(roles::add);

return roles;
}

@Nullable
protected static Set<String> claimValuesFromCtx(Map<String, Object> ctx)
{
Object value = (ctx == null) ? null : ctx.get(RoleProviderUtil.ROLE_CLAIM_CONTEXT_KEY);
if (!(value instanceof Set)) {
return null;
}
Set<?> rawClaimValues = (Set<?>) value;

Set<String> result = new HashSet<>();
for (Object claimValue : rawClaimValues) {
if (!(claimValue instanceof String)) {
return null;
}
String str = ((String) claimValue).trim();
if (!str.isEmpty()) {
result.add(str);
}
}
return result.isEmpty() ? null : result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.druid.security.authorization;

import org.apache.druid.security.basic.authorization.MetadataStoreRoleProvider;
import org.apache.druid.security.basic.authorization.RoleProviderUtil;
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
import org.apache.druid.server.security.AuthenticationResult;
import org.junit.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;

public class MetadataStoreRoleProviderGetRolesTest
{

@Test
public void returnsRolesByClaimValuesWhenPresent()
{
Map<String, BasicAuthorizerRole> roles = new HashMap<>();
roles.put("admin", null);
roles.put("viewer", null);

Set<String> viewerRole = Set.of("viewer");

BasicAuthorizerUser user = new BasicAuthorizerUser("alice", viewerRole);

Map<String, BasicAuthorizerUser> users = Map.of("alice", user);

BasicAuthorizerCacheManager cache = new StubCacheManager(users, roles);
MetadataStoreRoleProvider provider = new MetadataStoreRoleProvider(cache);

Set<String> claims = Set.of("admin", "extraneous");

Map<String, Object> ctx = Map.of(RoleProviderUtil.ROLE_CLAIM_CONTEXT_KEY, claims);

AuthenticationResult ar = new AuthenticationResult("alice", "basic", "pac4j", ctx);

Set<String> out = provider.getRoles("basic", ar);
Set<String> expected = Set.of("admin");
assertEquals(expected, out);
}

@Test
public void fallsBackToIdentityWhenNoClaimContext()
{
Set<String> viewerRole = Set.of("viewer");
BasicAuthorizerUser user = new BasicAuthorizerUser("alice", viewerRole);

Map<String, BasicAuthorizerUser> users = Map.of("alice", user);

Map<String, BasicAuthorizerRole> roles = new HashMap<>();
roles.put("admin", null);

BasicAuthorizerCacheManager cache = new StubCacheManager(users, roles);
MetadataStoreRoleProvider provider = new MetadataStoreRoleProvider(cache);

AuthenticationResult ar = new AuthenticationResult("alice", "basic", "pac4j", Collections.emptyMap());

Set<String> out = provider.getRoles("basic", ar);
Set<String> expected = Set.of("viewer");
assertEquals(expected, out);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.druid.security.authorization;

import org.apache.druid.security.basic.authorization.RoleProviderUtil;
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;


public class RoleProviderUtilTest
{

@Test
public void getRolesByIdentityAddsRolesWhenUserFound()
{
Set<String> roles = Set.of("r1", "r2");
BasicAuthorizerUser user = new BasicAuthorizerUser("id", roles);

Map<String, BasicAuthorizerUser> userMap = Map.of("id", user);

Set<String> out = RoleProviderUtil.getRolesByIdentity(userMap, "id");
assertEquals(roles, out);
}

@Test
public void getRolesByIdentityNoopWhenUserMissing()
{
Map<String, BasicAuthorizerUser> userMap = Map.of();
Set<String> out = RoleProviderUtil.getRolesByIdentity(userMap, "missing");
assertTrue(out.isEmpty());
}

@Test
public void getRolesByClaimValuesFiltersByRoleNames()
{
Map<String, BasicAuthorizerRole> roles = new HashMap<>();
roles.put("r1", null);
roles.put("r2", null);

BasicAuthorizerCacheManager cache = new StubCacheManager(Map.of(), roles);

Set<String> claims = Set.of("r2", "nope");
Set<String> out = RoleProviderUtil.getRolesByClaimValue("authz", claims, cache);
assertEquals(Set.of("r2"), out);
}

@Test
public void getRolesByClaimValuesThrowsWhenRoleMapNull()
{
BasicAuthorizerCacheManager cache = new StubCacheManager(Map.of(), null);
assertTrue(RoleProviderUtil.getRolesByClaimValue("authz", Set.of("r2"), cache).isEmpty());
}
}
Loading
Loading