mirror of https://github.com/apache/druid.git
Basic auth extension (#5099)
* Basic auth extension * Add auth configuration integration test * Fix missing authorizerName property * PR comments * Fix missing @JsonProperty annotation * PR comments * more PR comments
This commit is contained in:
parent
3b4395a16e
commit
f48c9d7be1
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.benchmark;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
import org.openjdk.jmh.infra.Blackhole;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
@Fork(value = 1)
|
||||||
|
@Warmup(iterations = 10)
|
||||||
|
@Measurement(iterations = 25)
|
||||||
|
public class BasicAuthUserMapSerdeBenchmark
|
||||||
|
{
|
||||||
|
@Param({"1000"})
|
||||||
|
private int numUsers;
|
||||||
|
|
||||||
|
private ObjectMapper smileMapper;
|
||||||
|
private Map<String, BenchmarkUser> userMap;
|
||||||
|
private List<byte[]> serializedUsers;
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
public void setup() throws IOException
|
||||||
|
{
|
||||||
|
smileMapper = new ObjectMapper(new SmileFactory());
|
||||||
|
userMap = new HashMap<>();
|
||||||
|
for (int i = 0; i < numUsers; i++) {
|
||||||
|
BenchmarkUser user = makeUser();
|
||||||
|
userMap.put(user.getName(), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedUsers = new ArrayList<>();
|
||||||
|
for (BenchmarkUser user : userMap.values()) {
|
||||||
|
byte[] serializedUser = smileMapper.writeValueAsBytes(user);
|
||||||
|
serializedUsers.add(serializedUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void serialize(Blackhole blackhole) throws Exception
|
||||||
|
{
|
||||||
|
for (BenchmarkUser user : userMap.values()) {
|
||||||
|
byte[] serializedUser = smileMapper.writeValueAsBytes(user);
|
||||||
|
blackhole.consume(serializedUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void deserialize(Blackhole blackhole) throws Exception
|
||||||
|
{
|
||||||
|
for (byte[] serializedUser : serializedUsers) {
|
||||||
|
BenchmarkUser user = smileMapper.readValue(serializedUser, BenchmarkUser.class);
|
||||||
|
blackhole.consume(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BenchmarkUser makeUser()
|
||||||
|
{
|
||||||
|
byte[] salt = new byte[32];
|
||||||
|
byte[] hash = new byte[64];
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
random.nextBytes(salt);
|
||||||
|
random.nextBytes(hash);
|
||||||
|
return new BenchmarkUser(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
new BenchmarkCredentials(
|
||||||
|
salt,
|
||||||
|
hash,
|
||||||
|
10000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BenchmarkUser
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final BenchmarkCredentials credentials;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BenchmarkUser(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("credentials") BenchmarkCredentials credentials
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.credentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public BenchmarkCredentials getCredentials()
|
||||||
|
{
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BenchmarkCredentials
|
||||||
|
{
|
||||||
|
private final byte[] salt;
|
||||||
|
private final byte[] hash;
|
||||||
|
private final int iterations;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BenchmarkCredentials(
|
||||||
|
@JsonProperty("salt") byte[] salt,
|
||||||
|
@JsonProperty("hash") byte[] hash,
|
||||||
|
@JsonProperty("iterations") int iterations
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.salt = salt;
|
||||||
|
this.hash = hash;
|
||||||
|
this.iterations = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSalt()
|
||||||
|
{
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getHash()
|
||||||
|
{
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public int getIterations()
|
||||||
|
{
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.benchmark;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.druid.jackson.DefaultObjectMapper;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
import org.openjdk.jmh.infra.Blackhole;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
@Fork(value = 1)
|
||||||
|
@Warmup(iterations = 10)
|
||||||
|
@Measurement(iterations = 25)
|
||||||
|
public class RegexMatchBenchmark
|
||||||
|
{
|
||||||
|
@Param({"100000"})
|
||||||
|
private int numPatterns;
|
||||||
|
|
||||||
|
private ObjectMapper jsonMapper;
|
||||||
|
|
||||||
|
private List<String> uuids;
|
||||||
|
|
||||||
|
private String granularityPathRegex = "^.*[Yy]=(\\d{4})/(?:[Mm]=(\\d{2})/(?:[Dd]=(\\d{2})/(?:[Hh]=(\\d{2})/(?:[Mm]=(\\d{2})/(?:[Ss]=(\\d{2})/)?)?)?)?)?.*$";
|
||||||
|
private String uuidRegex = "[\\w]{8}-[\\w]{4}-[\\w]{4}-[\\w]{4}-[\\w]{12}";
|
||||||
|
private Pattern uuidPattern = Pattern.compile(uuidRegex);
|
||||||
|
private Pattern granularityPathPattern = Pattern.compile(granularityPathRegex);
|
||||||
|
private byte[] uuidPatternBytes;
|
||||||
|
private byte[] granularityPathPatternBytes;
|
||||||
|
private String randomUUID = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
public void setup() throws IOException
|
||||||
|
{
|
||||||
|
jsonMapper = new DefaultObjectMapper();
|
||||||
|
|
||||||
|
uuids = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numPatterns; i++) {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
uuids.add(uuid.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
uuidPatternBytes = jsonMapper.writeValueAsBytes(uuidPattern);
|
||||||
|
granularityPathPatternBytes = jsonMapper.writeValueAsBytes(granularityPathPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileUUIDsAsRegex(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Pattern pattern = Pattern.compile(uuid);
|
||||||
|
blackhole.consume(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileUUIDsAsRegexAndMatchRandomUUID(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Pattern pattern = Pattern.compile(uuid);
|
||||||
|
Matcher matcher = pattern.matcher(randomUUID);
|
||||||
|
blackhole.consume(matcher.matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileGranularityPathRegex(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPatterns; i++) {
|
||||||
|
Pattern pattern = Pattern.compile(granularityPathRegex);
|
||||||
|
blackhole.consume(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void deserializeGranularityPathRegex(final Blackhole blackhole) throws IOException
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPatterns; i++) {
|
||||||
|
Pattern pattern = jsonMapper.readValue(granularityPathPatternBytes, Pattern.class);
|
||||||
|
blackhole.consume(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileUUIDRegex(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPatterns; i++) {
|
||||||
|
Pattern pattern = Pattern.compile(uuidRegex);
|
||||||
|
blackhole.consume(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void deserializeUUIDRegex(final Blackhole blackhole) throws IOException
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numPatterns; i++) {
|
||||||
|
Pattern pattern = jsonMapper.readValue(uuidPatternBytes, Pattern.class);
|
||||||
|
blackhole.consume(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileUUIDRegexAndMatch(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Pattern pattern = Pattern.compile(uuidRegex);
|
||||||
|
Matcher matcher = pattern.matcher(uuid);
|
||||||
|
blackhole.consume(matcher.matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void compileGranularityPathRegexAndMatch(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Pattern pattern = Pattern.compile(granularityPathRegex);
|
||||||
|
Matcher matcher = pattern.matcher(uuid);
|
||||||
|
blackhole.consume(matcher.matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void precompileUUIDRegexAndMatch(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Matcher matcher = uuidPattern.matcher(uuid);
|
||||||
|
blackhole.consume(matcher.matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
public void precompileGranularityPathRegexAndMatch(final Blackhole blackhole)
|
||||||
|
{
|
||||||
|
for (String uuid : uuids) {
|
||||||
|
Matcher matcher = granularityPathPattern.matcher(uuid);
|
||||||
|
blackhole.consume(matcher.matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expresses a single compare-and-swap update for MetadataStorageConnector's compareAndSwap method
|
||||||
|
*/
|
||||||
|
public class MetadataCASUpdate
|
||||||
|
{
|
||||||
|
private final String tableName;
|
||||||
|
private final String keyColumn;
|
||||||
|
private final String valueColumn;
|
||||||
|
private final String key;
|
||||||
|
private final byte[] oldValue;
|
||||||
|
private final byte[] newValue;
|
||||||
|
|
||||||
|
public MetadataCASUpdate(
|
||||||
|
String tableName,
|
||||||
|
String keyColumn,
|
||||||
|
String valueColumn,
|
||||||
|
String key,
|
||||||
|
byte[] oldValue,
|
||||||
|
byte[] newValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.keyColumn = keyColumn;
|
||||||
|
this.valueColumn = valueColumn;
|
||||||
|
this.key = key;
|
||||||
|
this.oldValue = oldValue;
|
||||||
|
this.newValue = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableName()
|
||||||
|
{
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyColumn()
|
||||||
|
{
|
||||||
|
return keyColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValueColumn()
|
||||||
|
{
|
||||||
|
return valueColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey()
|
||||||
|
{
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOldValue()
|
||||||
|
{
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getNewValue()
|
||||||
|
{
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,10 +19,15 @@
|
||||||
|
|
||||||
package io.druid.metadata;
|
package io.druid.metadata;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public interface MetadataStorageConnector
|
public interface MetadataStorageConnector
|
||||||
{
|
{
|
||||||
|
String CONFIG_TABLE_KEY_COLUMN = "name";
|
||||||
|
String CONFIG_TABLE_VALUE_COLUMN = "payload";
|
||||||
|
|
||||||
Void insertOrUpdate(
|
Void insertOrUpdate(
|
||||||
String tableName,
|
String tableName,
|
||||||
String keyColumn,
|
String keyColumn,
|
||||||
|
@ -38,6 +43,21 @@ public interface MetadataStorageConnector
|
||||||
String key
|
String key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomic compare-and-swap variant of insertOrUpdate().
|
||||||
|
*
|
||||||
|
* @param updates Set of updates to be made. If compare checks succeed for all updates, perform all updates.
|
||||||
|
* If any compare check fails, reject all updates.
|
||||||
|
* @return true if updates were made, false otherwise
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
default boolean compareAndSwap(
|
||||||
|
List<MetadataCASUpdate> updates
|
||||||
|
) throws Exception
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("compareAndSwap is not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
void createDataSourceTable();
|
void createDataSourceTable();
|
||||||
|
|
||||||
void createPendingSegmentsTable();
|
void createPendingSegmentsTable();
|
||||||
|
|
|
@ -110,6 +110,8 @@
|
||||||
<argument>io.druid.extensions:druid-examples</argument>
|
<argument>io.druid.extensions:druid-examples</argument>
|
||||||
<argument>-c</argument>
|
<argument>-c</argument>
|
||||||
<argument>io.druid.extensions:simple-client-sslcontext</argument>
|
<argument>io.druid.extensions:simple-client-sslcontext</argument>
|
||||||
|
<argument>-c</argument>
|
||||||
|
<argument>io.druid.extensions:druid-basic-security</argument>
|
||||||
<argument>${druid.distribution.pulldeps.opts}</argument>
|
<argument>${druid.distribution.pulldeps.opts}</argument>
|
||||||
</arguments>
|
</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
---
|
||||||
|
layout: doc_page
|
||||||
|
---
|
||||||
|
|
||||||
|
# Druid Basic Security
|
||||||
|
|
||||||
|
This extension adds:
|
||||||
|
- an Authenticator which supports [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
||||||
|
- an Authorizer which implements basic role-based access control
|
||||||
|
|
||||||
|
Make sure to [include](../../operations/including-extensions.html) `druid-basic-security` as an extension.
|
||||||
|
|
||||||
|
Please see [Authentication and Authorization](../../configuration/auth.html) for more information on the extension interfaces being implemented.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The examples in the section will use "MyBasicAuthenticator" and "MyBasicAuthorizer" as names for the Authenticator and Authorizer.
|
||||||
|
|
||||||
|
These properties are not tied to specific Authenticator or Authorizer instances.
|
||||||
|
|
||||||
|
These configuration properties should be added to the common runtime properties file.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.auth.basic.common.pollingPeriod`|Defines in milliseconds how often nodes should poll the coordinator for the current authenticator/authorizer database state.|60000|No|
|
||||||
|
|`druid.auth.basic.common.maxRandomDelay`|Defines in milliseconds the amount of random delay to add to the pollingPeriod, to spread polling requests across time.|6000|No|
|
||||||
|
|`druid.auth.basic.common.maxSyncRetries`|Determines how many times a service will retry if the authentication/authorization database state sync with the coordinator fails.|10|No|
|
||||||
|
|`druid.auth.basic.common.cacheDirectory`|If defined, snapshots of the basic Authenticator and Authorizer database caches will be stored on disk in this directory. If this property is defined, when a service is starting, it will attempt to initialize its caches from these on-disk snapshots, if the service is unable to initialize its state by communicating with the coordinator.|null|No|
|
||||||
|
|
||||||
|
|
||||||
|
### Creating an Authenticator
|
||||||
|
```
|
||||||
|
druid.auth.authenticatorChain=["MyBasicAuthenticator"]
|
||||||
|
|
||||||
|
druid.auth.authenticator.MyBasicAuthenticator.type=basic
|
||||||
|
druid.auth.authenticator.MyBasicAuthenticator.initialAdminPassword=password1
|
||||||
|
druid.auth.authenticator.MyBasicAuthenticator.initialInternalClientPassword=password2
|
||||||
|
druid.auth.authenticator.MyBasicAuthenticator.authorizerName=MyBasicAuthorizer
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the Basic authenticator, add an authenticator with type `basic` to the authenticatorChain.
|
||||||
|
|
||||||
|
Configuration of the named authenticator is assigned through properties with the form:
|
||||||
|
|
||||||
|
```
|
||||||
|
druid.auth.authenticator.<authenticatorName>.<authenticatorProperty>
|
||||||
|
```
|
||||||
|
|
||||||
|
The configuration examples in the rest of this document will use "MyBasicAuthenticator" as the name of the authenticator being configured.
|
||||||
|
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.initialAdminPassword`|Initial password for the automatically created default admin user. If no password is specified, the default admin user will not be created. If the default admin user already exists, setting this property will affect its password.|null|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.initialInternalClientPassword`|Initial password for the default internal system user, used for internal node communication. If no password is specified, the default internal system user will not be created. If the default internal system user already exists, setting this property will affect its password.|null|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.enableCacheNotifications`|If true, the coordinator will notify Druid nodes whenever a configuration change to this Authenticator occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.cacheNotificationTimeout`|The timeout in milliseconds for the cache notifications.|5000|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.credentialIterations`|Number of iterations to use for password hashing.|10000|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicAuthenticator.authorizerName`|Authorizer that requests should be directed to|N/A|Yes|
|
||||||
|
|
||||||
|
### Creating an Escalator
|
||||||
|
|
||||||
|
```
|
||||||
|
# Escalator
|
||||||
|
druid.escalator.type=basic
|
||||||
|
druid.escalator.internalClientUsername=druid_system
|
||||||
|
druid.escalator.internalClientPassword=password2
|
||||||
|
druid.escalator.authorizerName=MyBasicAuthorizer
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.escalator.internalClientUsername`|The escalator will use this username for requests made as the internal systerm user.|n/a|Yes|
|
||||||
|
|`druid.escalator.internalClientPassword`|The escalator will use this password for requests made as the internal system user.|n/a|Yes|
|
||||||
|
|`druid.escalator.authorizerName`|Authorizer that requests should be directed to.|n/a|Yes|
|
||||||
|
|
||||||
|
|
||||||
|
### Creating an Authorizer
|
||||||
|
```
|
||||||
|
druid.auth.authorizers=["MyBasicAuthorizer"]
|
||||||
|
|
||||||
|
druid.auth.authorizer.MyBasicAuthorizer.type=basic
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the Basic authorizer, add an authenticator with type `basic` to the authorizers list.
|
||||||
|
|
||||||
|
Configuration of the named authenticator is assigned through properties with the form:
|
||||||
|
|
||||||
|
```
|
||||||
|
druid.auth.authorizer.<authorizerName>.<authorizerProperty>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.auth.authorizer.MyBasicAuthorizer.enableCacheNotifications`|If true, the coordinator will notify Druid nodes whenever a configuration change to this Authorizer occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicAuthorizer.cacheNotificationTimeout`|The timeout in milliseconds for the cache notifications.|5000|No|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Coordinator Security API
|
||||||
|
To use these APIs, a user needs read/write permissions for the CONFIG resource type with name "security".
|
||||||
|
|
||||||
|
#### Authentication API
|
||||||
|
|
||||||
|
Root path: `/druid-ext/basic-security/authentication`
|
||||||
|
|
||||||
|
Each API endpoint includes {authenticatorName}, specifying which Authenticator instance is being configured.
|
||||||
|
|
||||||
|
##### User/Credential Management
|
||||||
|
`GET(/druid-ext/basic-security/authentication/db/{authenticatorName}/users)`
|
||||||
|
Return a list of all user names.
|
||||||
|
|
||||||
|
`GET(/druid-ext/basic-security/authentication/db/{authenticatorName}/users/{userName})`
|
||||||
|
Return the name and credentials information of the user with name {userName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authentication/db/{authenticatorName}/users/{userName})`
|
||||||
|
Create a new user with name {userName}
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authentication/db/{authenticatorName}/users/{userName})`
|
||||||
|
Delete the user with name {userName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authentication/db/{authenticatorName}/users/{userName}/credentials)`
|
||||||
|
Assign a password used for HTTP basic authentication for {userName}
|
||||||
|
Content: JSON password request object
|
||||||
|
|
||||||
|
Example request body:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"password": "helloworld"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Cache Load Status
|
||||||
|
`GET(/druid-ext/basic-security/authentication/loadStatus)`
|
||||||
|
Return the current load status of the local caches of the authentication database.
|
||||||
|
|
||||||
|
#### Authorization API
|
||||||
|
|
||||||
|
Root path: `/druid-ext/basic-security/authorization`
|
||||||
|
|
||||||
|
Each API endpoint includes {authorizerName}, specifying which Authorizer instance is being configured.
|
||||||
|
|
||||||
|
##### User Creation/Deletion
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/users)`
|
||||||
|
Return a list of all user names.
|
||||||
|
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})`
|
||||||
|
Return the name and role information of the user with name {userName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})`
|
||||||
|
Create a new user with name {userName}
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})`
|
||||||
|
Delete the user with name {userName}
|
||||||
|
|
||||||
|
|
||||||
|
#### Role Creation/Deletion
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/roles)`
|
||||||
|
Return a list of all role names.
|
||||||
|
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName})`
|
||||||
|
Return name and permissions for the role named {roleName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName})`
|
||||||
|
Create a new role with name {roleName}.
|
||||||
|
Content: username string
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName})`
|
||||||
|
Delete the role with name {roleName}.
|
||||||
|
|
||||||
|
|
||||||
|
#### Role Assignment
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName}/roles/{roleName})`
|
||||||
|
Assign role {roleName} to user {userName}.
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName}/roles/{roleName})`
|
||||||
|
Unassign role {roleName} from user {userName}
|
||||||
|
|
||||||
|
|
||||||
|
#### Permissions
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName}/permissions)`
|
||||||
|
Set the permissions of {roleName}. This replaces the previous set of permissions on the role.
|
||||||
|
|
||||||
|
Content: List of JSON Resource-Action objects, e.g.:
|
||||||
|
```
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"name": "wiki.*",
|
||||||
|
"type": "DATASOURCE"
|
||||||
|
},
|
||||||
|
"action": "READ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": {
|
||||||
|
"name": "wikiticker",
|
||||||
|
"type": "DATASOURCE"
|
||||||
|
},
|
||||||
|
"action": "WRITE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The "name" field for resources in the permission definitions are regexes used to match resource names during authorization checks.
|
||||||
|
|
||||||
|
Please see [Defining permissions](#defining-permissions) for more details.
|
||||||
|
|
||||||
|
##### Cache Load Status
|
||||||
|
`GET(/druid-ext/basic-security/authorization/loadStatus)`
|
||||||
|
Return the current load status of the local caches of the authorization database.
|
||||||
|
|
||||||
|
## Default user accounts
|
||||||
|
|
||||||
|
### Authenticator
|
||||||
|
If `druid.auth.authenticator.<authenticator-name>.initialAdminPassword` is set, a default admin user named "admin" will be created, with the specified initial password. If this configuration is omitted, the "admin" user will not be created.
|
||||||
|
|
||||||
|
If `druid.auth.authenticator.<authenticator-name>.initialInternalClientPassword` is set, a default internal system user named "druid_system" will be created, with the specified initial password. If this configuration is omitted, the "druid_system" user will not be created.
|
||||||
|
|
||||||
|
|
||||||
|
### Authorizer
|
||||||
|
|
||||||
|
Each Authorizer will always have a default "admin" and "druid_system" user with full privileges.
|
||||||
|
|
||||||
|
## Defining permissions
|
||||||
|
|
||||||
|
There are two action types in Druid: READ and WRITE
|
||||||
|
|
||||||
|
There are three resource types in Druid: DATASOURCE, CONFIG, and STATE.
|
||||||
|
|
||||||
|
### DATASOURCE
|
||||||
|
Resource names for this type are datasource names. Specifying a datasource permission allows the administrator to grant users access to specific datasources.
|
||||||
|
|
||||||
|
### CONFIG
|
||||||
|
There are two possible resource names for the "CONFIG" resource type, "CONFIG" and "security". Granting a user access to CONFIG resources allows them to access the following endpoints.
|
||||||
|
|
||||||
|
"CONFIG" resource name covers the following endpoints:
|
||||||
|
|
||||||
|
|Endpoint|Node Type|
|
||||||
|
|--------|---------|
|
||||||
|
|`/druid/coordinator/v1/config`|coordinator|
|
||||||
|
|`/druid/indexer/v1/worker`|overlord|
|
||||||
|
|`/druid/indexer/v1/worker/history`|overlord|
|
||||||
|
|`/druid/worker/v1/disable`|middleManager|
|
||||||
|
|`/druid/worker/v1/enable`|middleManager|
|
||||||
|
|
||||||
|
"security" resource name covers the following endpoint:
|
||||||
|
|
||||||
|
|Endpoint|Node Type|
|
||||||
|
|--------|---------|
|
||||||
|
|`/druid/coordinator/v1/security`|coordinator|
|
||||||
|
|
||||||
|
### STATE
|
||||||
|
There is only one possible resource name for the "STATE" config resource type, "STATE". Granting a user access to STATE resources allows them to access the following endpoints.
|
||||||
|
|
||||||
|
"STATE" resource name covers the following endpoints:
|
||||||
|
|
||||||
|
|Endpoint|Node Type|
|
||||||
|
|--------|---------|
|
||||||
|
|`/druid/coordinator/v1`|coordinator|
|
||||||
|
|`/druid/coordinator/v1/rules`|coordinator|
|
||||||
|
|`/druid/coordinator/v1/rules/history`|coordinator|
|
||||||
|
|`/druid/coordinator/v1/servers`|coordinator|
|
||||||
|
|`/druid/coordinator/v1/tiers`|coordinator|
|
||||||
|
|`/druid/broker/v1`|broker|
|
||||||
|
|`/druid/v2/candidates`|broker|
|
||||||
|
|`/druid/indexer/v1/leader`|overlord|
|
||||||
|
|`/druid/indexer/v1/isLeader`|overlord|
|
||||||
|
|`/druid/indexer/v1/action`|overlord|
|
||||||
|
|`/druid/indexer/v1/workers`|overlord|
|
||||||
|
|`/druid/indexer/v1/scaling`|overlord|
|
||||||
|
|`/druid/worker/v1/enabled`|middleManager|
|
||||||
|
|`/druid/worker/v1/tasks`|middleManager|
|
||||||
|
|`/druid/worker/v1/task/{taskid}/shutdown`|middleManager|
|
||||||
|
|`/druid/worker/v1//task/{taskid}/log`|middleManager|
|
||||||
|
|`/druid/historical/v1`|historical|
|
||||||
|
|`/druid-internal/v1/segments/`|historical|
|
||||||
|
|`/druid-internal/v1/segments/`|peon|
|
||||||
|
|`/druid-internal/v1/segments/`|realtime|
|
||||||
|
|`/status`|all nodes|
|
||||||
|
|
||||||
|
## Configuration Propagation
|
||||||
|
|
||||||
|
To prevent excessive load on the coordinator, the Authenticator and Authorizer user/role database state is cached on each Druid node.
|
||||||
|
|
||||||
|
Each node will periodically poll the coordinator for the latest database state, controlled by the `druid.auth.basic.common.pollingPeriod` and `druid.auth.basic.common.maxRandomDelay` properties.
|
||||||
|
|
||||||
|
When a configuration update occurs, the coordinator can optionally notify each node with the updated database state. This behavior is controlled by the `enableCacheNotifications` and `cacheNotificationTimeout` properties on Authenticators and Authorizers.
|
||||||
|
|
||||||
|
Note that because of the caching, changes made to the user/role database may not be immediately reflected at each Druid node.
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Druid - a distributed column store.
|
||||||
|
~ Copyright 2012 - 2015 Metamarkets Group Inc.
|
||||||
|
~
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>io.druid.extensions</groupId>
|
||||||
|
<artifactId>druid-basic-security</artifactId>
|
||||||
|
<name>druid-basic-security</name>
|
||||||
|
<description>druid-basic-security</description>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>io.druid</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
<version>0.11.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.druid</groupId>
|
||||||
|
<artifactId>druid-services</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.druid</groupId>
|
||||||
|
<artifactId>druid-server</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jdbi</groupId>
|
||||||
|
<artifactId>jdbi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.easymock</groupId>
|
||||||
|
<artifactId>easymock</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.druid</groupId>
|
||||||
|
<artifactId>druid-server</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public class BasicAuthCommonCacheConfig
|
||||||
|
{
|
||||||
|
private static final long DEFAULT_POLLING_PERIOD = 60000;
|
||||||
|
private static final long DEFAULT_MAX_RANDOM_DELAY = DEFAULT_POLLING_PERIOD / 10;
|
||||||
|
private static final int DEFAULT_MAX_SYNC_RETRIES = 10;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final long pollingPeriod;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final long maxRandomDelay;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final String cacheDirectory;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final int maxSyncRetries;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthCommonCacheConfig(
|
||||||
|
@JsonProperty("pollingPeriod") Long pollingPeriod,
|
||||||
|
@JsonProperty("maxRandomDelay") Long maxRandomDelay,
|
||||||
|
@JsonProperty("cacheDirectory") String cacheDirectory,
|
||||||
|
@JsonProperty("maxSyncRetries") Integer maxSyncRetries
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.pollingPeriod = pollingPeriod == null ? DEFAULT_POLLING_PERIOD : pollingPeriod;
|
||||||
|
this.maxRandomDelay = maxRandomDelay == null ? DEFAULT_MAX_RANDOM_DELAY : maxRandomDelay;
|
||||||
|
this.cacheDirectory = cacheDirectory;
|
||||||
|
this.maxSyncRetries = maxSyncRetries == null ? DEFAULT_MAX_SYNC_RETRIES : maxSyncRetries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public long getPollingPeriod()
|
||||||
|
{
|
||||||
|
return pollingPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public long getMaxRandomDelay()
|
||||||
|
{
|
||||||
|
return maxRandomDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getCacheDirectory()
|
||||||
|
{
|
||||||
|
return cacheDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public int getMaxSyncRetries()
|
||||||
|
{
|
||||||
|
return maxSyncRetries;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
public class BasicAuthDBConfig
|
||||||
|
{
|
||||||
|
public static final long DEFAULT_CACHE_NOTIFY_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
|
private final String initialAdminPassword;
|
||||||
|
private final String initialInternalClientPassword;
|
||||||
|
private final boolean enableCacheNotifications;
|
||||||
|
private final long cacheNotificationTimeout;
|
||||||
|
private final int iterations;
|
||||||
|
|
||||||
|
public BasicAuthDBConfig(
|
||||||
|
final String initialAdminPassword,
|
||||||
|
final String initialInternalClientPassword,
|
||||||
|
final Boolean enableCacheNotifications,
|
||||||
|
final Long cacheNotificationTimeout,
|
||||||
|
final int iterations
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.initialAdminPassword = initialAdminPassword;
|
||||||
|
this.initialInternalClientPassword = initialInternalClientPassword;
|
||||||
|
this.enableCacheNotifications = enableCacheNotifications;
|
||||||
|
this.cacheNotificationTimeout = cacheNotificationTimeout;
|
||||||
|
this.iterations = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitialAdminPassword()
|
||||||
|
{
|
||||||
|
return initialAdminPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitialInternalClientPassword()
|
||||||
|
{
|
||||||
|
return initialInternalClientPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableCacheNotifications()
|
||||||
|
{
|
||||||
|
return enableCacheNotifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCacheNotificationTimeout()
|
||||||
|
{
|
||||||
|
return cacheNotificationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIterations()
|
||||||
|
{
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BasicAuthUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Logger log = new Logger(BasicAuthUtils.class);
|
||||||
|
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||||
|
public final static String ADMIN_NAME = "admin";
|
||||||
|
public final static String INTERNAL_USER_NAME = "druid_system";
|
||||||
|
|
||||||
|
// PBKDF2WithHmacSHA512 is chosen since it has built-in support in Java8.
|
||||||
|
// Argon2 (https://github.com/p-h-c/phc-winner-argon2) is newer but the only presently
|
||||||
|
// available Java binding is LGPLv3 licensed.
|
||||||
|
// Key length is 512-bit to match the PBKDF2WithHmacSHA512 algorithm.
|
||||||
|
// 256-bit salt should be more than sufficient for uniqueness, expected user count is on the order of thousands.
|
||||||
|
public final static int SALT_LENGTH = 32;
|
||||||
|
public final static int DEFAULT_KEY_ITERATIONS = 10000;
|
||||||
|
public final static int KEY_LENGTH = 512;
|
||||||
|
public final static String ALGORITHM = "PBKDF2WithHmacSHA512";
|
||||||
|
|
||||||
|
public static final TypeReference AUTHENTICATOR_USER_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<Map<String, BasicAuthenticatorUser>>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final TypeReference AUTHORIZER_USER_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<Map<String, BasicAuthorizerUser>>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final TypeReference AUTHORIZER_ROLE_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<Map<String, BasicAuthorizerRole>>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final TypeReference AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<UserAndRoleMap>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String getEncodedCredentials(final String unencodedCreds)
|
||||||
|
{
|
||||||
|
return Base64.getEncoder().encodeToString(StringUtils.toUtf8(unencodedCreds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hashPassword(final char[] password, final byte[] salt, final int iterations)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
|
||||||
|
SecretKey key = keyFactory.generateSecret(
|
||||||
|
new PBEKeySpec(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
|
KEY_LENGTH
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return key.getEncoded();
|
||||||
|
}
|
||||||
|
catch (InvalidKeySpecException ikse) {
|
||||||
|
log.error("WTF? invalid keyspec");
|
||||||
|
throw new RuntimeException("WTF? invalid keyspec", ikse);
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException nsae) {
|
||||||
|
log.error("%s not supported on this system.", ALGORITHM);
|
||||||
|
throw new RuntimeException(StringUtils.format("%s not supported on this system.", ALGORITHM), nsae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] generateSalt()
|
||||||
|
{
|
||||||
|
byte salt[] = new byte[SALT_LENGTH];
|
||||||
|
SECURE_RANDOM.nextBytes(salt);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getBasicUserSecretFromHttpReq(HttpServletRequest httpReq)
|
||||||
|
{
|
||||||
|
String authHeader = httpReq.getHeader("Authorization");
|
||||||
|
|
||||||
|
if (authHeader == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authHeader.length() < 7) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authHeader.substring(0, 6).equals("Basic ")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String encodedUserSecret = authHeader.substring(6);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return StringUtils.fromUtf8(Base64.getDecoder().decode(encodedUserSecret));
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException iae) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, BasicAuthenticatorUser> deserializeAuthenticatorUserMap(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
byte[] userMapBytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap;
|
||||||
|
if (userMapBytes == null) {
|
||||||
|
userMap = Maps.newHashMap();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
userMap = objectMapper.readValue(userMapBytes, AUTHENTICATOR_USER_MAP_TYPE_REFERENCE);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] serializeAuthenticatorUserMap(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsBytes(userMap);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new ISE(ioe, "WTF? Couldn't serialize userMap!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, BasicAuthorizerUser> deserializeAuthorizerUserMap(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
byte[] userMapBytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerUser> userMap;
|
||||||
|
if (userMapBytes == null) {
|
||||||
|
userMap = Maps.newHashMap();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
userMap = objectMapper.readValue(userMapBytes, BasicAuthUtils.AUTHORIZER_USER_MAP_TYPE_REFERENCE);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] serializeAuthorizerUserMap(ObjectMapper objectMapper, Map<String, BasicAuthorizerUser> userMap)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsBytes(userMap);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new ISE(ioe, "WTF? Couldn't serialize userMap!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, BasicAuthorizerRole> deserializeAuthorizerRoleMap(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
byte[] roleMapBytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap;
|
||||||
|
if (roleMapBytes == null) {
|
||||||
|
roleMap = Maps.newHashMap();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
roleMap = objectMapper.readValue(roleMapBytes, BasicAuthUtils.AUTHORIZER_ROLE_MAP_TYPE_REFERENCE);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] serializeAuthorizerRoleMap(ObjectMapper objectMapper, Map<String, BasicAuthorizerRole> roleMap)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsBytes(roleMap);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new ISE(ioe, "WTF? Couldn't serialize roleMap!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw this for invalid resource accesses in the druid-basic-security extension that are likely a result of user error
|
||||||
|
* (e.g., entry not found, duplicate entries).
|
||||||
|
*/
|
||||||
|
public class BasicSecurityDBResourceException extends IllegalArgumentException
|
||||||
|
{
|
||||||
|
public BasicSecurityDBResourceException(String formatText, Object... arguments)
|
||||||
|
{
|
||||||
|
super(StringUtils.nonStrictFormat(formatText, arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicSecurityDBResourceException(Throwable t, String formatText, Object... arguments)
|
||||||
|
{
|
||||||
|
super(StringUtils.nonStrictFormat(formatText, arguments), t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
import io.druid.guice.Jerseys;
|
||||||
|
import io.druid.guice.JsonConfigProvider;
|
||||||
|
import io.druid.guice.LazySingleton;
|
||||||
|
import io.druid.guice.LifecycleModule;
|
||||||
|
import io.druid.initialization.DruidModule;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPEscalator;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheNotifier;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.CoordinatorBasicAuthenticatorCacheNotifier;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.CoordinatorPollingBasicAuthenticatorCacheManager;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.MetadataStoragePollingBasicAuthenticatorCacheManager;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.BasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.CoordinatorBasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.BasicAuthenticatorResource;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.BasicAuthenticatorResourceHandler;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.CoordinatorBasicAuthenticatorResourceHandler;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.DefaultBasicAuthenticatorResourceHandler;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.CoordinatorBasicAuthorizerCacheNotifier;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.CoordinatorPollingBasicAuthorizerCacheManager;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.MetadataStoragePollingBasicAuthorizerCacheManager;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.BasicAuthorizerResource;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.BasicAuthorizerResourceHandler;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.CoordinatorBasicAuthorizerResourceHandler;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.DefaultBasicAuthorizerResourceHandler;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BasicSecurityDruidModule implements DruidModule
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
JsonConfigProvider.bind(binder, "druid.auth.basic.common", BasicAuthCommonCacheConfig.class);
|
||||||
|
|
||||||
|
LifecycleModule.register(binder, BasicAuthenticatorMetadataStorageUpdater.class);
|
||||||
|
LifecycleModule.register(binder, BasicAuthorizerMetadataStorageUpdater.class);
|
||||||
|
LifecycleModule.register(binder, BasicAuthenticatorCacheManager.class);
|
||||||
|
LifecycleModule.register(binder, BasicAuthorizerCacheManager.class);
|
||||||
|
|
||||||
|
Jerseys.addResource(binder, BasicAuthenticatorResource.class);
|
||||||
|
Jerseys.addResource(binder, BasicAuthorizerResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthenticatorMetadataStorageUpdater createAuthenticatorStorageUpdater(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthenticatorMetadataStorageUpdater.class);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthenticatorCacheManager createAuthenticatorCacheManager(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(MetadataStoragePollingBasicAuthenticatorCacheManager.class);
|
||||||
|
} else {
|
||||||
|
return injector.getInstance(CoordinatorPollingBasicAuthenticatorCacheManager.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthenticatorResourceHandler createAuthenticatorResourceHandler(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthenticatorResourceHandler.class);
|
||||||
|
} else {
|
||||||
|
return injector.getInstance(DefaultBasicAuthenticatorResourceHandler.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthenticatorCacheNotifier createAuthenticatorCacheNotifier(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthenticatorCacheNotifier.class);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthorizerMetadataStorageUpdater createAuthorizerStorageUpdater(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthorizerMetadataStorageUpdater.class);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthorizerCacheManager createAuthorizerCacheManager(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(MetadataStoragePollingBasicAuthorizerCacheManager.class);
|
||||||
|
} else {
|
||||||
|
return injector.getInstance(CoordinatorPollingBasicAuthorizerCacheManager.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthorizerResourceHandler createAuthorizerResourceHandler(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthorizerResourceHandler.class);
|
||||||
|
} else {
|
||||||
|
return injector.getInstance(DefaultBasicAuthorizerResourceHandler.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides @LazySingleton
|
||||||
|
public static BasicAuthorizerCacheNotifier createAuthorizerCacheNotifier(final Injector injector)
|
||||||
|
{
|
||||||
|
if (isCoordinator(injector)) {
|
||||||
|
return injector.getInstance(CoordinatorBasicAuthorizerCacheNotifier.class);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends Module> getJacksonModules()
|
||||||
|
{
|
||||||
|
return ImmutableList.of(
|
||||||
|
new SimpleModule("BasicDruidSecurity").registerSubtypes(
|
||||||
|
BasicHTTPAuthenticator.class,
|
||||||
|
BasicHTTPEscalator.class,
|
||||||
|
BasicRoleBasedAuthorizer.class
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCoordinator(Injector injector)
|
||||||
|
{
|
||||||
|
final String serviceName;
|
||||||
|
try {
|
||||||
|
serviceName = injector.getInstance(Key.get(String.class, Names.named("serviceName")));
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "druid/coordinator".equals(serviceName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.sun.jersey.spi.container.ContainerRequest;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.server.http.security.AbstractResourceFilter;
|
||||||
|
import io.druid.server.security.Access;
|
||||||
|
import io.druid.server.security.AuthorizationUtils;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BasicSecurityResourceFilter extends AbstractResourceFilter
|
||||||
|
{
|
||||||
|
private static final List<String> APPLICABLE_PATHS = ImmutableList.of(
|
||||||
|
"/druid-ext/basic-security/authentication",
|
||||||
|
"/druid-ext/basic-security/authorization"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final String SECURITY_RESOURCE_NAME = "security";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BasicSecurityResourceFilter(
|
||||||
|
AuthorizerMapper authorizerMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
super(authorizerMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContainerRequest filter(ContainerRequest request)
|
||||||
|
{
|
||||||
|
final ResourceAction resourceAction = new ResourceAction(
|
||||||
|
new Resource(SECURITY_RESOURCE_NAME, ResourceType.CONFIG),
|
||||||
|
getAction(request)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Access authResult = AuthorizationUtils.authorizeResourceAction(
|
||||||
|
getReq(),
|
||||||
|
resourceAction,
|
||||||
|
getAuthorizerMapper()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!authResult.isAllowed()) {
|
||||||
|
throw new WebApplicationException(
|
||||||
|
Response.status(Response.Status.FORBIDDEN)
|
||||||
|
.entity(StringUtils.format("Access-Check-Result: %s", authResult.toString()))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(String requestPath)
|
||||||
|
{
|
||||||
|
for (String path : APPLICABLE_PATHS) {
|
||||||
|
if (requestPath.startsWith(path) && !requestPath.equals(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import com.metamx.http.client.HttpClient;
|
||||||
|
import com.metamx.http.client.Request;
|
||||||
|
import com.metamx.http.client.response.ClientResponse;
|
||||||
|
import com.metamx.http.client.response.HttpResponseHandler;
|
||||||
|
import com.metamx.http.client.response.StatusResponseHolder;
|
||||||
|
import io.druid.discovery.DiscoveryDruidNode;
|
||||||
|
import io.druid.discovery.DruidNodeDiscovery;
|
||||||
|
import io.druid.discovery.DruidNodeDiscoveryProvider;
|
||||||
|
import io.druid.java.util.common.Pair;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.concurrent.Execs;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.server.DruidNode;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpChunk;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class CommonCacheNotifier
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG = new EmittingLogger(CommonCacheNotifier.class);
|
||||||
|
|
||||||
|
private static final List<String> NODE_TYPES = Arrays.asList(
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_BROKER,
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_OVERLORD,
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_HISTORICAL,
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_PEON,
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_ROUTER,
|
||||||
|
DruidNodeDiscoveryProvider.NODE_TYPE_MM
|
||||||
|
);
|
||||||
|
|
||||||
|
private final DruidNodeDiscoveryProvider discoveryProvider;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final BlockingQueue<Pair<String, byte[]>> updateQueue;
|
||||||
|
private final Map<String, BasicAuthDBConfig> itemConfigMap;
|
||||||
|
private final String baseUrl;
|
||||||
|
private final String callerName;
|
||||||
|
private final ExecutorService exec;
|
||||||
|
|
||||||
|
public CommonCacheNotifier(
|
||||||
|
Map<String, BasicAuthDBConfig> itemConfigMap,
|
||||||
|
DruidNodeDiscoveryProvider discoveryProvider,
|
||||||
|
HttpClient httpClient,
|
||||||
|
String baseUrl,
|
||||||
|
String callerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.exec = Execs.scheduledSingleThreaded(StringUtils.format("%s-notifierThread-", callerName) + "%d");
|
||||||
|
this.callerName = callerName;
|
||||||
|
this.updateQueue = new LinkedBlockingQueue<>();
|
||||||
|
this.itemConfigMap = itemConfigMap;
|
||||||
|
this.discoveryProvider = discoveryProvider;
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
exec.submit(
|
||||||
|
() -> {
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
try {
|
||||||
|
LOG.debug(callerName + ":Waiting for cache update notification");
|
||||||
|
Pair<String, byte[]> update = updateQueue.take();
|
||||||
|
String authorizer = update.lhs;
|
||||||
|
byte[] serializedMap = update.rhs;
|
||||||
|
BasicAuthDBConfig authorizerConfig = itemConfigMap.get(update.lhs);
|
||||||
|
if (!authorizerConfig.isEnableCacheNotifications()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(callerName + ":Sending cache update notifications");
|
||||||
|
// Best effort, if a notification fails, the remote node will eventually poll to update its state
|
||||||
|
// We wait for responses however, to avoid flooding remote nodes with notifications.
|
||||||
|
List<ListenableFuture<StatusResponseHolder>> futures = sendUpdate(
|
||||||
|
authorizer,
|
||||||
|
serializedMap
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<StatusResponseHolder> responses = Futures.allAsList(futures)
|
||||||
|
.get(
|
||||||
|
authorizerConfig.getCacheNotificationTimeout(),
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
);
|
||||||
|
|
||||||
|
for (StatusResponseHolder response : responses) {
|
||||||
|
LOG.debug(callerName + ":Got status: " + response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, callerName + ":Failed to get response for cache notification.").emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(callerName + ":Received responses for cache update notifications.");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, callerName + ":Error occured while handling updates for cachedUserMaps.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
exec.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUpdate(String updatedItemName, byte[] updatedItemData)
|
||||||
|
{
|
||||||
|
updateQueue.add(
|
||||||
|
new Pair<>(updatedItemName, updatedItemData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ListenableFuture<StatusResponseHolder>> sendUpdate(String updatedAuthorizerPrefix, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
List<ListenableFuture<StatusResponseHolder>> futures = new ArrayList<>();
|
||||||
|
for (String nodeType : NODE_TYPES) {
|
||||||
|
DruidNodeDiscovery nodeDiscovery = discoveryProvider.getForNodeType(nodeType);
|
||||||
|
Collection<DiscoveryDruidNode> nodes = nodeDiscovery.getAllNodes();
|
||||||
|
for (DiscoveryDruidNode node : nodes) {
|
||||||
|
URL listenerURL = getListenerURL(node.getDruidNode(), baseUrl, updatedAuthorizerPrefix);
|
||||||
|
|
||||||
|
// best effort, if this fails, remote node will poll and pick up the update eventually
|
||||||
|
Request req = new Request(HttpMethod.POST, listenerURL);
|
||||||
|
req.setContent(MediaType.APPLICATION_JSON, serializedUserMap);
|
||||||
|
|
||||||
|
BasicAuthDBConfig itemConfig = itemConfigMap.get(updatedAuthorizerPrefix);
|
||||||
|
|
||||||
|
ListenableFuture<StatusResponseHolder> future = httpClient.go(
|
||||||
|
req,
|
||||||
|
new ResponseHandler(),
|
||||||
|
Duration.millis(itemConfig.getCacheNotificationTimeout())
|
||||||
|
);
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL getListenerURL(DruidNode druidNode, String baseUrl, String itemName)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return new URL(
|
||||||
|
druidNode.getServiceScheme(),
|
||||||
|
druidNode.getHost(),
|
||||||
|
druidNode.getPortToUse(),
|
||||||
|
StringUtils.format(baseUrl, itemName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (MalformedURLException mue) {
|
||||||
|
LOG.error(callerName + ":WTF? Malformed url for DruidNode[%s] and itemName[%s]", druidNode, itemName);
|
||||||
|
throw new RuntimeException(mue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based off StatusResponseHandler, but with response content ignored
|
||||||
|
private static class ResponseHandler implements HttpResponseHandler<StatusResponseHolder, StatusResponseHolder>
|
||||||
|
{
|
||||||
|
protected static final Logger log = new Logger(ResponseHandler.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResponse<StatusResponseHolder> handleResponse(HttpResponse response)
|
||||||
|
{
|
||||||
|
return ClientResponse.unfinished(
|
||||||
|
new StatusResponseHolder(
|
||||||
|
response.getStatus(),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResponse<StatusResponseHolder> handleChunk(
|
||||||
|
ClientResponse<StatusResponseHolder> response,
|
||||||
|
HttpChunk chunk
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResponse<StatusResponseHolder> done(ClientResponse<StatusResponseHolder> response)
|
||||||
|
{
|
||||||
|
return ClientResponse.finished(response.getObj());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(
|
||||||
|
ClientResponse<StatusResponseHolder> clientResponse, Throwable e
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Its safe to Ignore as the ClientResponse returned in handleChunk were unfinished
|
||||||
|
log.error(e, "exceptionCaught in CommonCacheNotifier ResponseHandler.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import io.druid.java.util.common.IAE;
|
||||||
|
import io.druid.security.basic.BasicAuthDBConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.server.security.AuthConfig;
|
||||||
|
import io.druid.server.security.AuthenticationResult;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@JsonTypeName("basic")
|
||||||
|
public class BasicHTTPAuthenticator implements Authenticator
|
||||||
|
{
|
||||||
|
private final Provider<BasicAuthenticatorCacheManager> cacheManager;
|
||||||
|
private final String name;
|
||||||
|
private final String authorizerName;
|
||||||
|
private final BasicAuthDBConfig dbConfig;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicHTTPAuthenticator(
|
||||||
|
@JacksonInject Provider<BasicAuthenticatorCacheManager> cacheManager,
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("authorizerName") String authorizerName,
|
||||||
|
@JsonProperty("initialAdminPassword") String initialAdminPassword,
|
||||||
|
@JsonProperty("initialInternalClientPassword") String initialInternalClientPassword,
|
||||||
|
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
||||||
|
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout,
|
||||||
|
@JsonProperty("credentialIterations") Integer credentialIterations
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.authorizerName = authorizerName;
|
||||||
|
this.dbConfig = new BasicAuthDBConfig(
|
||||||
|
initialAdminPassword,
|
||||||
|
initialInternalClientPassword,
|
||||||
|
enableCacheNotifications == null ? true : enableCacheNotifications,
|
||||||
|
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
||||||
|
credentialIterations == null ? BasicAuthUtils.DEFAULT_KEY_ITERATIONS : credentialIterations
|
||||||
|
);
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter getFilter()
|
||||||
|
{
|
||||||
|
return new BasicHTTPAuthenticationFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthChallengeHeader()
|
||||||
|
{
|
||||||
|
return "Basic";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public AuthenticationResult authenticateJDBCContext(Map<String, Object> context)
|
||||||
|
{
|
||||||
|
String user = (String) context.get("user");
|
||||||
|
String password = (String) context.get("password");
|
||||||
|
|
||||||
|
if (user == null || password == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkCredentials(user, password.toCharArray())) {
|
||||||
|
return new AuthenticationResult(user, name, null);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Filter> getFilterClass()
|
||||||
|
{
|
||||||
|
return BasicHTTPAuthenticationFilter.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getInitParameters()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath()
|
||||||
|
{
|
||||||
|
return "/*";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EnumSet<DispatcherType> getDispatcherType()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicAuthDBConfig getDbConfig()
|
||||||
|
{
|
||||||
|
return dbConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BasicHTTPAuthenticationFilter implements Filter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(
|
||||||
|
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
|
||||||
|
) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
HttpServletResponse httpResp = (HttpServletResponse) servletResponse;
|
||||||
|
String userSecret = BasicAuthUtils.getBasicUserSecretFromHttpReq((HttpServletRequest) servletRequest);
|
||||||
|
|
||||||
|
if (userSecret == null) {
|
||||||
|
// Request didn't have HTTP Basic auth credentials, move on to the next filter
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] splits = userSecret.split(":");
|
||||||
|
if (splits.length != 2) {
|
||||||
|
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String user = splits[0];
|
||||||
|
char[] password = splits[1].toCharArray();
|
||||||
|
|
||||||
|
if (checkCredentials(user, password)) {
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult(user, authorizerName, null);
|
||||||
|
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkCredentials(String username, char[] password)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = cacheManager.get().getUserMap(name);
|
||||||
|
if (userMap == null) {
|
||||||
|
throw new IAE("No authenticator found with prefix: [%s]", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthenticatorUser user = userMap.get(username);
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BasicAuthenticatorCredentials credentials = user.getCredentials();
|
||||||
|
if (credentials == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
password,
|
||||||
|
credentials.getSalt(),
|
||||||
|
credentials.getIterations()
|
||||||
|
);
|
||||||
|
|
||||||
|
return Arrays.equals(recalculatedHash, credentials.getHash());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.metamx.http.client.CredentialedHttpClient;
|
||||||
|
import com.metamx.http.client.HttpClient;
|
||||||
|
import com.metamx.http.client.auth.BasicCredentials;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.server.security.AuthenticationResult;
|
||||||
|
import io.druid.server.security.Escalator;
|
||||||
|
import org.eclipse.jetty.client.api.Authentication;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.util.Attributes;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
@JsonTypeName("basic")
|
||||||
|
public class BasicHTTPEscalator implements Escalator
|
||||||
|
{
|
||||||
|
private final String internalClientUsername;
|
||||||
|
private final String internalClientPassword;
|
||||||
|
private final String authorizerName;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicHTTPEscalator(
|
||||||
|
@JsonProperty("authorizerName") String authorizerName,
|
||||||
|
@JsonProperty("internalClientUsername") String internalClientUsername,
|
||||||
|
@JsonProperty("internalClientPassword") String internalClientPassword
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.authorizerName = authorizerName;
|
||||||
|
this.internalClientUsername = internalClientUsername;
|
||||||
|
this.internalClientPassword = internalClientPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClient createEscalatedClient(HttpClient baseClient)
|
||||||
|
{
|
||||||
|
return new CredentialedHttpClient(
|
||||||
|
new BasicCredentials(internalClientUsername, internalClientPassword),
|
||||||
|
baseClient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.client.HttpClient createEscalatedJettyClient(org.eclipse.jetty.client.HttpClient baseClient)
|
||||||
|
{
|
||||||
|
baseClient.getAuthenticationStore().addAuthentication(new Authentication()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean matches(String type, URI uri, String realm)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result authenticate(
|
||||||
|
final Request request, ContentResponse response, Authentication.HeaderInfo headerInfo, Attributes context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new Result()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public URI getURI()
|
||||||
|
{
|
||||||
|
return request.getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(Request request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
final String unencodedCreds = StringUtils.format("%s:%s", internalClientUsername, internalClientPassword);
|
||||||
|
final String base64Creds = BasicAuthUtils.getEncodedCredentials(unencodedCreds);
|
||||||
|
request.getHeaders().add(HttpHeaders.Names.AUTHORIZATION, "Basic " + base64Creds);
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return baseClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationResult createEscalatedAuthenticationResult()
|
||||||
|
{
|
||||||
|
return new AuthenticationResult(internalClientUsername, authorizerName, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication;
|
||||||
|
|
||||||
|
import com.metamx.http.client.response.ClientResponse;
|
||||||
|
import com.metamx.http.client.response.FullResponseHolder;
|
||||||
|
import com.metamx.http.client.response.HttpResponseHandler;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpChunk;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
|
||||||
|
public class BytesFullResponseHandler implements HttpResponseHandler<FullResponseHolder, FullResponseHolder>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ClientResponse<FullResponseHolder> handleResponse(HttpResponse response)
|
||||||
|
{
|
||||||
|
BytesFullResponseHolder holder = new BytesFullResponseHolder(
|
||||||
|
response.getStatus(),
|
||||||
|
response,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
holder.addChunk(response.getContent().array());
|
||||||
|
|
||||||
|
return ClientResponse.unfinished(
|
||||||
|
holder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResponse<FullResponseHolder> handleChunk(
|
||||||
|
ClientResponse<FullResponseHolder> response,
|
||||||
|
HttpChunk chunk
|
||||||
|
)
|
||||||
|
{
|
||||||
|
BytesFullResponseHolder holder = (BytesFullResponseHolder) response.getObj();
|
||||||
|
|
||||||
|
if (holder == null) {
|
||||||
|
return ClientResponse.finished(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.addChunk(chunk.getContent().array());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResponse<FullResponseHolder> done(ClientResponse<FullResponseHolder> response)
|
||||||
|
{
|
||||||
|
return ClientResponse.finished(response.getObj());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(
|
||||||
|
ClientResponse<FullResponseHolder> clientResponse, Throwable e
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Its safe to Ignore as the ClientResponse returned in handleChunk were unfinished
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication;
|
||||||
|
|
||||||
|
import com.metamx.http.client.response.FullResponseHolder;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BytesFullResponseHolder extends FullResponseHolder
|
||||||
|
{
|
||||||
|
private final List<byte[]> chunks;
|
||||||
|
|
||||||
|
public BytesFullResponseHolder(
|
||||||
|
HttpResponseStatus status,
|
||||||
|
HttpResponse response,
|
||||||
|
StringBuilder builder
|
||||||
|
)
|
||||||
|
{
|
||||||
|
super(status, response, builder);
|
||||||
|
this.chunks = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChunk(byte[] chunk)
|
||||||
|
{
|
||||||
|
chunks.add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes()
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
for (byte[] chunk : chunks) {
|
||||||
|
size += chunk.length;
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(new byte[size]);
|
||||||
|
|
||||||
|
for (byte[] chunk : chunks) {
|
||||||
|
buf.put(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.array();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.cache;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is reponsible for maintaining a cache of the authenticator database state. The BasicHTTPAuthenticator
|
||||||
|
* uses an injected BasicAuthenticatorCacheManager to make its authentication decisions.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthenticatorCacheManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Update this cache manager's local state with fresh information pushed by the coordinator.
|
||||||
|
*
|
||||||
|
* @param authenticatorPrefix The name of the authenticator this update applies to.
|
||||||
|
* @param serializedUserMap The updated, serialized user map
|
||||||
|
*/
|
||||||
|
void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cache manager's local view of the user map for the authenticator named `authenticatorPrefix`.
|
||||||
|
*
|
||||||
|
* @param authenticatorPrefix The name of the authenticator
|
||||||
|
* @return User map
|
||||||
|
*/
|
||||||
|
Map<String, BasicAuthenticatorUser> getUserMap(String authenticatorPrefix);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification to druid services, containing updated authenticator user map state.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthenticatorCacheNotifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send the user map state contained in updatedUserMap to all non-coordinator Druid services
|
||||||
|
*
|
||||||
|
* @param updatedAuthenticatorPrefix Name of authenticator being updated
|
||||||
|
* @param updatedUserMap User map state
|
||||||
|
*/
|
||||||
|
void addUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap);
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.cache;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import com.metamx.http.client.HttpClient;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.discovery.DruidNodeDiscoveryProvider;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.EscalatedClient;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.security.basic.BasicAuthDBConfig;
|
||||||
|
import io.druid.security.basic.CommonCacheNotifier;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG = new EmittingLogger(CoordinatorBasicAuthenticatorCacheNotifier.class);
|
||||||
|
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
private CommonCacheNotifier cacheNotifier;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthenticatorCacheNotifier(
|
||||||
|
AuthenticatorMapper authenticatorMapper,
|
||||||
|
DruidNodeDiscoveryProvider discoveryProvider,
|
||||||
|
@EscalatedClient HttpClient httpClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
cacheNotifier = new CommonCacheNotifier(
|
||||||
|
initAuthenticatorConfigMap(authenticatorMapper),
|
||||||
|
discoveryProvider,
|
||||||
|
httpClient,
|
||||||
|
"/druid-ext/basic-security/authentication/listen/%s",
|
||||||
|
"CoordinatorBasicAuthenticatorCacheNotifier"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cacheNotifier.start();
|
||||||
|
lifecycleLock.started();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cacheNotifier.stop();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUpdate(String updatedAuthorizerPrefix, byte[] updatedUserMap)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
cacheNotifier.addUpdate(updatedAuthorizerPrefix, updatedUserMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BasicAuthDBConfig> initAuthenticatorConfigMap(AuthenticatorMapper mapper)
|
||||||
|
{
|
||||||
|
Preconditions.checkNotNull(mapper);
|
||||||
|
Preconditions.checkNotNull(mapper.getAuthenticatorMap());
|
||||||
|
|
||||||
|
Map<String, BasicAuthDBConfig> authenticatorConfigMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Authenticator> entry : mapper.getAuthenticatorMap().entrySet()) {
|
||||||
|
Authenticator authenticator = entry.getValue();
|
||||||
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
|
String authenticatorName = entry.getKey();
|
||||||
|
BasicHTTPAuthenticator basicHTTPAuthenticator = (BasicHTTPAuthenticator) authenticator;
|
||||||
|
BasicAuthDBConfig dbConfig = basicHTTPAuthenticator.getDbConfig();
|
||||||
|
authenticatorConfigMap.put(authenticatorName, dbConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authenticatorConfigMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.cache;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import com.metamx.http.client.Request;
|
||||||
|
import io.druid.client.coordinator.Coordinator;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.discovery.DruidLeaderClient;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.RetryUtils;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.concurrent.Execs;
|
||||||
|
import io.druid.java.util.common.concurrent.ScheduledExecutors;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.BytesFullResponseHandler;
|
||||||
|
import io.druid.security.basic.authentication.BytesFullResponseHolder;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache manager for non-coordinator services that polls the coordinator for authentication database state.
|
||||||
|
*/
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAuthenticatorCacheManager
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG = new EmittingLogger(CoordinatorPollingBasicAuthenticatorCacheManager.class);
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, Map<String, BasicAuthenticatorUser>> cachedUserMaps;
|
||||||
|
private final Set<String> authenticatorPrefixes;
|
||||||
|
private final Injector injector;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
private final DruidLeaderClient druidLeaderClient;
|
||||||
|
private final BasicAuthCommonCacheConfig commonCacheConfig;
|
||||||
|
private final ScheduledExecutorService exec;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorPollingBasicAuthenticatorCacheManager(
|
||||||
|
Injector injector,
|
||||||
|
BasicAuthCommonCacheConfig commonCacheConfig,
|
||||||
|
@Smile ObjectMapper objectMapper,
|
||||||
|
@Coordinator DruidLeaderClient druidLeaderClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.exec = Execs.scheduledSingleThreaded("BasicAuthenticatorCacheManager-Exec--%d");
|
||||||
|
this.injector = injector;
|
||||||
|
this.commonCacheConfig = commonCacheConfig;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
|
this.authenticatorPrefixes = new HashSet<>();
|
||||||
|
this.druidLeaderClient = druidLeaderClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Starting DefaultBasicAuthenticatorCacheManager.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
initUserMaps();
|
||||||
|
|
||||||
|
ScheduledExecutors.scheduleWithFixedDelay(
|
||||||
|
exec,
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
|
||||||
|
LOG.debug("Inserting random polling delay of [%s] ms", randomDelay);
|
||||||
|
Thread.sleep(randomDelay);
|
||||||
|
|
||||||
|
LOG.debug("Scheduled cache poll is running");
|
||||||
|
for (String authenticatorPrefix : authenticatorPrefixes) {
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorPrefix, false);
|
||||||
|
if (userMap != null) {
|
||||||
|
cachedUserMaps.put(authenticatorPrefix, userMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Scheduled cache poll is done");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
lifecycleLock.started();
|
||||||
|
LOG.info("Started DefaultBasicAuthenticatorCacheManager.");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
throw new ISE("can't stop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("DefaultBasicAuthenticatorCacheManager is stopping.");
|
||||||
|
exec.shutdown();
|
||||||
|
LOG.info("DefaultBasicAuthenticatorCacheManager is stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
LOG.debug("Received cache update for authenticator [%s].", authenticatorPrefix);
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
try {
|
||||||
|
cachedUserMaps.put(
|
||||||
|
authenticatorPrefix,
|
||||||
|
objectMapper.readValue(
|
||||||
|
serializedUserMap,
|
||||||
|
BasicAuthUtils.AUTHENTICATOR_USER_MAP_TYPE_REFERENCE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeUserMapToDisk(authenticatorPrefix, serializedUserMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "WTF? Could not deserialize user map received from coordinator.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthenticatorUser> getUserMap(String authenticatorPrefix)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
return cachedUserMaps.get(authenticatorPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Map<String, BasicAuthenticatorUser> fetchUserMapFromCoordinator(String prefix, boolean isInit)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return RetryUtils.retry(
|
||||||
|
() -> {
|
||||||
|
return tryFetchUserMapFromCoordinator(prefix);
|
||||||
|
},
|
||||||
|
e -> true,
|
||||||
|
commonCacheConfig.getMaxSyncRetries()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "Encountered exception while fetching user map for authenticator [%s]", prefix);
|
||||||
|
if (isInit) {
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
try {
|
||||||
|
LOG.info("Attempting to load user map snapshot from disk.");
|
||||||
|
return loadUserMapFromDisk(prefix);
|
||||||
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
e2.addSuppressed(e);
|
||||||
|
LOG.makeAlert(e2, "Encountered exception while loading user map snapshot for authenticator [%s]", prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserMapFilename(String prefix)
|
||||||
|
{
|
||||||
|
return StringUtils.format("%s.authenticator.cache", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Map<String, BasicAuthenticatorUser> loadUserMapFromDisk(String prefix) throws IOException
|
||||||
|
{
|
||||||
|
File userMapFile = new File(commonCacheConfig.getCacheDirectory(), getUserMapFilename(prefix));
|
||||||
|
if (!userMapFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(
|
||||||
|
userMapFile,
|
||||||
|
BasicAuthUtils.AUTHENTICATOR_USER_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeUserMapToDisk(String prefix, byte[] userMapBytes) throws IOException
|
||||||
|
{
|
||||||
|
File cacheDir = new File(commonCacheConfig.getCacheDirectory());
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
File userMapFile = new File(commonCacheConfig.getCacheDirectory(), getUserMapFilename(prefix));
|
||||||
|
Files.write(userMapBytes, userMapFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BasicAuthenticatorUser> tryFetchUserMapFromCoordinator(String prefix) throws Exception
|
||||||
|
{
|
||||||
|
Request req = druidLeaderClient.makeRequest(
|
||||||
|
HttpMethod.GET,
|
||||||
|
StringUtils.format("/druid-ext/basic-security/authentication/db/%s/cachedSerializedUserMap", prefix)
|
||||||
|
);
|
||||||
|
BytesFullResponseHolder responseHolder = (BytesFullResponseHolder) druidLeaderClient.go(
|
||||||
|
req,
|
||||||
|
new BytesFullResponseHandler()
|
||||||
|
);
|
||||||
|
byte[] userMapBytes = responseHolder.getBytes();
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = objectMapper.readValue(
|
||||||
|
userMapBytes,
|
||||||
|
BasicAuthUtils.AUTHENTICATOR_USER_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
if (userMap != null && commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeUserMapToDisk(prefix, userMapBytes);
|
||||||
|
}
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUserMaps()
|
||||||
|
{
|
||||||
|
AuthenticatorMapper authenticatorMapper = injector.getInstance(AuthenticatorMapper.class);
|
||||||
|
|
||||||
|
if (authenticatorMapper == null || authenticatorMapper.getAuthenticatorMap() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, Authenticator> entry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
|
||||||
|
Authenticator authenticator = entry.getValue();
|
||||||
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
|
String authenticatorName = entry.getKey();
|
||||||
|
authenticatorPrefixes.add(authenticatorName);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorName, true);
|
||||||
|
if (userMap != null) {
|
||||||
|
cachedUserMaps.put(authenticatorName, userMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.cache;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.BasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used on coordinator nodes, reading from a BasicAuthenticatorMetadataStorageUpdater that has direct access to the
|
||||||
|
* metadata store.
|
||||||
|
*/
|
||||||
|
public class MetadataStoragePollingBasicAuthenticatorCacheManager implements BasicAuthenticatorCacheManager
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(MetadataStoragePollingBasicAuthenticatorCacheManager.class);
|
||||||
|
|
||||||
|
private final BasicAuthenticatorMetadataStorageUpdater storageUpdater;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MetadataStoragePollingBasicAuthenticatorCacheManager(
|
||||||
|
BasicAuthenticatorMetadataStorageUpdater storageUpdater
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.storageUpdater = storageUpdater;
|
||||||
|
|
||||||
|
log.info("Starting coordinator basic authenticator cache manager.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthenticatorUser> getUserMap(String authenticatorPrefix)
|
||||||
|
{
|
||||||
|
return storageUpdater.getCachedUserMap(authenticatorPrefix);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.updater;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this interface are responsible for connecting directly to the metadata storage,
|
||||||
|
* modifying the authenticator database state or reading it. This interface is used by the
|
||||||
|
* MetadataStoragePollingBasicAuthenticatorCacheManager (for reads) and the CoordinatorBasicAuthenticatorResourceHandler
|
||||||
|
* (for handling configuration read/writes).
|
||||||
|
*/
|
||||||
|
public interface BasicAuthenticatorMetadataStorageUpdater
|
||||||
|
{
|
||||||
|
void createUser(String prefix, String userName);
|
||||||
|
|
||||||
|
void deleteUser(String prefix, String userName);
|
||||||
|
|
||||||
|
void setUserCredentials(String prefix, String userName, BasicAuthenticatorCredentialUpdate update);
|
||||||
|
|
||||||
|
Map<String, BasicAuthenticatorUser> getCachedUserMap(String prefix);
|
||||||
|
|
||||||
|
byte[] getCachedSerializedUserMap(String prefix);
|
||||||
|
|
||||||
|
byte[] getCurrentUserMapBytes(String prefix);
|
||||||
|
|
||||||
|
void refreshAllNotification();
|
||||||
|
}
|
|
@ -0,0 +1,444 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.db.updater;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import io.druid.common.config.ConfigManager;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.concurrent.Execs;
|
||||||
|
import io.druid.java.util.common.concurrent.ScheduledExecutors;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.metadata.MetadataCASUpdate;
|
||||||
|
import io.druid.metadata.MetadataStorageConnector;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthDBConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheNotifier;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUserMapBundle;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements BasicAuthenticatorMetadataStorageUpdater
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG =
|
||||||
|
new EmittingLogger(CoordinatorBasicAuthenticatorMetadataStorageUpdater.class);
|
||||||
|
private static final String USERS = "users";
|
||||||
|
private static final long UPDATE_RETRY_DELAY = 1000;
|
||||||
|
|
||||||
|
private final AuthenticatorMapper authenticatorMapper;
|
||||||
|
private final MetadataStorageConnector connector;
|
||||||
|
private final MetadataStorageTablesConfig connectorConfig;
|
||||||
|
private final BasicAuthCommonCacheConfig commonCacheConfig;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final BasicAuthenticatorCacheNotifier cacheNotifier;
|
||||||
|
private final int numRetries = 5;
|
||||||
|
|
||||||
|
private final Map<String, BasicAuthenticatorUserMapBundle> cachedUserMaps;
|
||||||
|
private final Set<String> authenticatorPrefixes;
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
|
||||||
|
private final ScheduledExecutorService exec;
|
||||||
|
private volatile boolean stopped = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthenticatorMetadataStorageUpdater(
|
||||||
|
AuthenticatorMapper authenticatorMapper,
|
||||||
|
MetadataStorageConnector connector,
|
||||||
|
MetadataStorageTablesConfig connectorConfig,
|
||||||
|
BasicAuthCommonCacheConfig commonCacheConfig,
|
||||||
|
@Smile ObjectMapper objectMapper,
|
||||||
|
BasicAuthenticatorCacheNotifier cacheNotifier,
|
||||||
|
ConfigManager configManager // ConfigManager creates the db table we need, set a dependency here
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.exec = Execs.scheduledSingleThreaded("CoordinatorBasicAuthenticatorMetadataStorageUpdater-Exec--%d");
|
||||||
|
this.authenticatorMapper = authenticatorMapper;
|
||||||
|
this.connector = connector;
|
||||||
|
this.connectorConfig = connectorConfig;
|
||||||
|
this.commonCacheConfig = commonCacheConfig;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.cacheNotifier = cacheNotifier;
|
||||||
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
|
this.authenticatorPrefixes = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authenticatorMapper == null || authenticatorMapper.getAuthenticatorMap() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOG.info("Starting CoordinatorBasicAuthenticatorMetadataStorageUpdater.");
|
||||||
|
for (Map.Entry<String, Authenticator> entry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
|
||||||
|
Authenticator authenticator = entry.getValue();
|
||||||
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
|
String authenticatorName = entry.getKey();
|
||||||
|
authenticatorPrefixes.add(authenticatorName);
|
||||||
|
BasicHTTPAuthenticator basicHTTPAuthenticator = (BasicHTTPAuthenticator) authenticator;
|
||||||
|
BasicAuthDBConfig dbConfig = basicHTTPAuthenticator.getDbConfig();
|
||||||
|
byte[] userMapBytes = getCurrentUserMapBytes(authenticatorName);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
userMapBytes
|
||||||
|
);
|
||||||
|
cachedUserMaps.put(authenticatorName, new BasicAuthenticatorUserMapBundle(userMap, userMapBytes));
|
||||||
|
|
||||||
|
if (dbConfig.getInitialAdminPassword() != null && !userMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
||||||
|
createUserInternal(authenticatorName, BasicAuthUtils.ADMIN_NAME);
|
||||||
|
setUserCredentialsInternal(
|
||||||
|
authenticatorName,
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
new BasicAuthenticatorCredentialUpdate(
|
||||||
|
dbConfig.getInitialAdminPassword(),
|
||||||
|
BasicAuthUtils.DEFAULT_KEY_ITERATIONS
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbConfig.getInitialInternalClientPassword() != null
|
||||||
|
&& !userMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
|
||||||
|
createUserInternal(authenticatorName, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
setUserCredentialsInternal(
|
||||||
|
authenticatorName,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
new BasicAuthenticatorCredentialUpdate(
|
||||||
|
dbConfig.getInitialInternalClientPassword(),
|
||||||
|
BasicAuthUtils.DEFAULT_KEY_ITERATIONS
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduledExecutors.scheduleWithFixedDelay(
|
||||||
|
exec,
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Callable<ScheduledExecutors.Signal>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ScheduledExecutors.Signal call() throws Exception
|
||||||
|
{
|
||||||
|
if (stopped) {
|
||||||
|
return ScheduledExecutors.Signal.STOP;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
LOG.debug("Scheduled db poll is running");
|
||||||
|
for (String authenticatorPrefix : authenticatorPrefixes) {
|
||||||
|
|
||||||
|
byte[] userMapBytes = getCurrentUserMapBytes(authenticatorPrefix);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
userMapBytes
|
||||||
|
);
|
||||||
|
if (userMapBytes != null) {
|
||||||
|
cachedUserMaps.put(authenticatorPrefix, new BasicAuthenticatorUserMapBundle(userMap, userMapBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Scheduled db poll is done");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
}
|
||||||
|
return ScheduledExecutors.Signal.REPEAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
lifecycleLock.started();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
throw new ISE("can't stop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("CoordinatorBasicAuthenticatorMetadataStorageUpdater is stopping.");
|
||||||
|
stopped = true;
|
||||||
|
LOG.info("CoordinatorBasicAuthenticatorMetadataStorageUpdater is stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createUser(String prefix, String userName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
createUserInternal(prefix, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(String prefix, String userName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
deleteUserInternal(prefix, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserCredentials(String prefix, String userName, BasicAuthenticatorCredentialUpdate update)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
setUserCredentialsInternal(prefix, userName, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthenticatorUser> getCachedUserMap(String prefix)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
BasicAuthenticatorUserMapBundle bundle = cachedUserMaps.get(prefix);
|
||||||
|
if (bundle == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return bundle.getUserMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCachedSerializedUserMap(String prefix)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
BasicAuthenticatorUserMapBundle bundle = cachedUserMaps.get(prefix);
|
||||||
|
if (bundle == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return bundle.getSerializedUserMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCurrentUserMapBytes(String prefix)
|
||||||
|
{
|
||||||
|
return connector.lookup(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, USERS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshAllNotification()
|
||||||
|
{
|
||||||
|
cachedUserMaps.forEach(
|
||||||
|
(authenticatorName, userMapBundle) -> {
|
||||||
|
cacheNotifier.addUpdate(authenticatorName, userMapBundle.getSerializedUserMap());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getPrefixedKeyColumn(String keyPrefix, String keyName)
|
||||||
|
{
|
||||||
|
return StringUtils.format("basic_authentication_%s_%s", keyPrefix, keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateUserMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap,
|
||||||
|
byte[] oldValue,
|
||||||
|
byte[] newValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
MetadataCASUpdate update = new MetadataCASUpdate(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, USERS),
|
||||||
|
oldValue,
|
||||||
|
newValue
|
||||||
|
);
|
||||||
|
|
||||||
|
boolean succeeded = connector.compareAndSwap(
|
||||||
|
Collections.singletonList(update)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
cachedUserMaps.put(prefix, new BasicAuthenticatorUserMapBundle(userMap, newValue));
|
||||||
|
cacheNotifier.addUpdate(prefix, newValue);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUserInternal(String prefix, String userName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (createUserOnce(prefix, userName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not create user[%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteUserInternal(String prefix, String userName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (deleteUserOnce(prefix, userName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not delete user[%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUserCredentialsInternal(String prefix, String userName, BasicAuthenticatorCredentialUpdate update)
|
||||||
|
{
|
||||||
|
BasicAuthenticatorCredentials credentials;
|
||||||
|
|
||||||
|
// use default iteration count from Authenticator if not specified in request
|
||||||
|
if (update.getIterations() == -1) {
|
||||||
|
BasicHTTPAuthenticator authenticator = (BasicHTTPAuthenticator) authenticatorMapper.getAuthenticatorMap().get(
|
||||||
|
prefix
|
||||||
|
);
|
||||||
|
credentials = new BasicAuthenticatorCredentials(
|
||||||
|
new BasicAuthenticatorCredentialUpdate(
|
||||||
|
update.getPassword(),
|
||||||
|
authenticator.getDbConfig().getIterations()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
credentials = new BasicAuthenticatorCredentials(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (setUserCredentialOnce(prefix, userName, credentials)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not set credentials for user[%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createUserOnce(String prefix, String userName)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldValue
|
||||||
|
);
|
||||||
|
if (userMap.get(userName) != null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] already exists.", userName);
|
||||||
|
} else {
|
||||||
|
userMap.put(userName, new BasicAuthenticatorUser(userName, null));
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, userMap);
|
||||||
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteUserOnce(String prefix, String userName)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldValue
|
||||||
|
);
|
||||||
|
if (userMap.get(userName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
} else {
|
||||||
|
userMap.remove(userName);
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, userMap);
|
||||||
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setUserCredentialOnce(String prefix, String userName, BasicAuthenticatorCredentials credentials)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldValue
|
||||||
|
);
|
||||||
|
if (userMap.get(userName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
} else {
|
||||||
|
userMap.put(userName, new BasicAuthenticatorUser(userName, credentials));
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, userMap);
|
||||||
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.endpoint;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.sun.jersey.spi.container.ResourceFilters;
|
||||||
|
import io.druid.guice.LazySingleton;
|
||||||
|
import io.druid.security.basic.BasicSecurityResourceFilter;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
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.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("/druid-ext/basic-security/authentication")
|
||||||
|
@LazySingleton
|
||||||
|
public class BasicAuthenticatorResource
|
||||||
|
{
|
||||||
|
private final BasicAuthenticatorResourceHandler handler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BasicAuthenticatorResource(
|
||||||
|
BasicAuthenticatorResourceHandler handler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return Load status of authenticator DB caches
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/loadStatus")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getLoadStatus(
|
||||||
|
@Context HttpServletRequest req
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.getLoadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* Sends an "update" notification to all services with the current user database state,
|
||||||
|
* causing them to refresh their DB cache state.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/refreshAll")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response refreshAll(
|
||||||
|
@Context HttpServletRequest req
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.refreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return List of all users
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authenticatorName}/users")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getAllUsers(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.getAllUsers(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user to retrieve information about
|
||||||
|
*
|
||||||
|
* @return Name and credentials of the user with userName, 400 error response if user doesn't exist
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authenticatorName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName,
|
||||||
|
@PathParam("userName") final String userName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.getUser(authenticatorName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user with name userName
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name to assign the new user
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authenticatorName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response createUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName,
|
||||||
|
@PathParam("userName") String userName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.createUser(authenticatorName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user to delete
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user doesn't exist
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authenticatorName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response deleteUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName,
|
||||||
|
@PathParam("userName") String userName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.deleteUser(authenticatorName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign credentials for a user
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user
|
||||||
|
* @param password Password to assign
|
||||||
|
*
|
||||||
|
* @return OK response, 400 error if user doesn't exist
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authenticatorName}/users/{userName}/credentials")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response updateUserCredentials(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName,
|
||||||
|
@PathParam("userName") String userName,
|
||||||
|
BasicAuthenticatorCredentialUpdate update
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.updateUserCredentials(authenticatorName, userName, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return serialized user map
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authenticatorName}/cachedSerializedUserMap")
|
||||||
|
@Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getCachedSerializedUserMap(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.getCachedSerializedUserMap(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for update notifications for the auth storage
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name to assign the new user
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/listen/{authenticatorName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response authenticatorUpdateListener(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authenticatorName") final String authenticatorName,
|
||||||
|
byte[] serializedUserMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return handler.authenticatorUpdateListener(authenticatorName, serializedUserMap);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.endpoint;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles authenticator-related API calls. Coordinator and non-coordinator methods are combined here because of an
|
||||||
|
* inability to selectively inject jetty resources in configure(Binder binder) of the extension module based
|
||||||
|
* on node type.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthenticatorResourceHandler
|
||||||
|
{
|
||||||
|
// coordinator methods
|
||||||
|
Response getAllUsers(String authenticatorName);
|
||||||
|
|
||||||
|
Response getUser(String authenticatorName, String userName);
|
||||||
|
|
||||||
|
Response createUser(String authenticatorName, String userName);
|
||||||
|
|
||||||
|
Response deleteUser(String authenticatorName, String userName);
|
||||||
|
|
||||||
|
Response updateUserCredentials(String authenticatorName, String userName, BasicAuthenticatorCredentialUpdate update);
|
||||||
|
|
||||||
|
Response getCachedSerializedUserMap(String authenticatorName);
|
||||||
|
|
||||||
|
Response refreshAll();
|
||||||
|
|
||||||
|
// non-coordinator methods
|
||||||
|
Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap);
|
||||||
|
|
||||||
|
// common methods
|
||||||
|
Response getLoadStatus();
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.endpoint;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.BasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthenticatorResourceHandler implements BasicAuthenticatorResourceHandler
|
||||||
|
{
|
||||||
|
private final BasicAuthenticatorMetadataStorageUpdater storageUpdater;
|
||||||
|
private final Map<String, BasicHTTPAuthenticator> authenticatorMap;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthenticatorResourceHandler(
|
||||||
|
BasicAuthenticatorMetadataStorageUpdater storageUpdater,
|
||||||
|
AuthenticatorMapper authenticatorMapper,
|
||||||
|
@Smile ObjectMapper objectMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.storageUpdater = storageUpdater;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
|
||||||
|
this.authenticatorMap = Maps.newHashMap();
|
||||||
|
for (Map.Entry<String, Authenticator> authenticatorEntry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
|
||||||
|
final String authenticatorName = authenticatorEntry.getKey();
|
||||||
|
final Authenticator authenticator = authenticatorEntry.getValue();
|
||||||
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
|
authenticatorMap.put(
|
||||||
|
authenticatorName,
|
||||||
|
(BasicHTTPAuthenticator) authenticator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllUsers(
|
||||||
|
final String authenticatorName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authenticatorName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response.ok(userMap.keySet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authenticatorName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthenticatorUser user = userMap.get(userName);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
}
|
||||||
|
return Response.ok(user).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.createUser(authenticatorName, userName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.deleteUser(authenticatorName, userName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response updateUserCredentials(String authenticatorName, String userName, BasicAuthenticatorCredentialUpdate update)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.setUserCredentials(authenticatorName, userName, update);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedSerializedUserMap(String authenticatorName)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
return makeResponseForAuthenticatorNotFound(authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok(storageUpdater.getCachedSerializedUserMap(authenticatorName)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response refreshAll()
|
||||||
|
{
|
||||||
|
storageUpdater.refreshAllNotification();
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getLoadStatus()
|
||||||
|
{
|
||||||
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
|
authenticatorMap.forEach(
|
||||||
|
(authenticatorName, authenticator) -> {
|
||||||
|
loadStatus.put(authenticatorName, storageUpdater.getCachedUserMap(authenticatorName) != null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Response.ok(loadStatus).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response makeResponseForAuthenticatorNotFound(String authenticatorName)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error",
|
||||||
|
StringUtils.format("Basic authenticator with name [%s] does not exist.", authenticatorName)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response makeResponseForBasicSecurityDBResourceException(BasicSecurityDBResourceException bsre)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error", bsre.getMessage()
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.endpoint;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.server.security.Authenticator;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultBasicAuthenticatorResourceHandler implements BasicAuthenticatorResourceHandler
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(DefaultBasicAuthenticatorResourceHandler.class);
|
||||||
|
private static final Response NOT_FOUND_RESPONSE = Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
|
||||||
|
private final BasicAuthenticatorCacheManager cacheManager;
|
||||||
|
private final Map<String, BasicHTTPAuthenticator> authenticatorMap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultBasicAuthenticatorResourceHandler(
|
||||||
|
BasicAuthenticatorCacheManager cacheManager,
|
||||||
|
AuthenticatorMapper authenticatorMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
|
||||||
|
this.authenticatorMap = Maps.newHashMap();
|
||||||
|
for (Map.Entry<String, Authenticator> authenticatorEntry : authenticatorMapper.getAuthenticatorMap().entrySet()) {
|
||||||
|
final String authenticatorName = authenticatorEntry.getKey();
|
||||||
|
final Authenticator authenticator = authenticatorEntry.getValue();
|
||||||
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
|
authenticatorMap.put(
|
||||||
|
authenticatorName,
|
||||||
|
(BasicHTTPAuthenticator) authenticator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllUsers(String authenticatorName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteUser(String authenticatorName, String userName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response updateUserCredentials(
|
||||||
|
String authenticatorName,
|
||||||
|
String userName,
|
||||||
|
BasicAuthenticatorCredentialUpdate update
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedSerializedUserMap(String authenticatorName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response refreshAll()
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
|
if (authenticator == null) {
|
||||||
|
String errMsg = StringUtils.format("Received update for unknown authenticator[%s]", authenticatorName);
|
||||||
|
log.error(errMsg);
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error",
|
||||||
|
StringUtils.format(errMsg)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheManager.handleAuthenticatorUpdate(authenticatorName, serializedUserMap);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getLoadStatus()
|
||||||
|
{
|
||||||
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
|
authenticatorMap.forEach(
|
||||||
|
(authenticatorName, authenticator) -> {
|
||||||
|
loadStatus.put(authenticatorName, cacheManager.getUserMap(authenticatorName) != null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Response.ok(loadStatus).build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
public class BasicAuthenticatorCredentialUpdate
|
||||||
|
{
|
||||||
|
private final String password;
|
||||||
|
private final int iterations;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthenticatorCredentialUpdate(
|
||||||
|
@JsonProperty("password") String password,
|
||||||
|
@JsonProperty("iterations") Integer iterations
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Preconditions.checkNotNull(password, "Cannot assign null password.");
|
||||||
|
Preconditions.checkArgument(!StringUtils.isEmpty(password), "Cannot assign empty password.");
|
||||||
|
this.password = password;
|
||||||
|
this.iterations = iterations == null ? -1 : iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getPassword()
|
||||||
|
{
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public int getIterations()
|
||||||
|
{
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class BasicAuthenticatorCredentials
|
||||||
|
{
|
||||||
|
private final byte[] salt;
|
||||||
|
private final byte[] hash;
|
||||||
|
private final int iterations;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthenticatorCredentials(
|
||||||
|
@JsonProperty("salt") byte[] salt,
|
||||||
|
@JsonProperty("hash") byte[] hash,
|
||||||
|
@JsonProperty("iterations") int iterations
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Preconditions.checkNotNull(salt);
|
||||||
|
Preconditions.checkNotNull(hash);
|
||||||
|
this.salt = salt;
|
||||||
|
this.hash = hash;
|
||||||
|
this.iterations = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicAuthenticatorCredentials(BasicAuthenticatorCredentialUpdate update)
|
||||||
|
{
|
||||||
|
this.iterations = update.getIterations();
|
||||||
|
this.salt = BasicAuthUtils.generateSalt();
|
||||||
|
this.hash = BasicAuthUtils.hashPassword(update.getPassword().toCharArray(), salt, iterations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSalt()
|
||||||
|
{
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getHash()
|
||||||
|
{
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public int getIterations()
|
||||||
|
{
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthenticatorCredentials that = (BasicAuthenticatorCredentials) o;
|
||||||
|
|
||||||
|
if (getIterations() != that.getIterations()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(getSalt(), that.getSalt())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Arrays.equals(getHash(), that.getHash());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = Arrays.hashCode(getSalt());
|
||||||
|
result = 31 * result + Arrays.hashCode(getHash());
|
||||||
|
result = 31 * result + getIterations();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public class BasicAuthenticatorUser
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final BasicAuthenticatorCredentials credentials;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthenticatorUser(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("credentials") BasicAuthenticatorCredentials credentials
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.credentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public BasicAuthenticatorCredentials getCredentials()
|
||||||
|
{
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthenticatorUser that = (BasicAuthenticatorUser) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getCredentials() != null ? getCredentials().equals(that.getCredentials()) : that.getCredentials() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
|
result = 31 * result + (getCredentials() != null ? getCredentials().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authentication.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BasicAuthenticatorUserMapBundle
|
||||||
|
{
|
||||||
|
private final Map<String, BasicAuthenticatorUser> userMap;
|
||||||
|
private final byte[] serializedUserMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthenticatorUserMapBundle(
|
||||||
|
@JsonProperty("userMap") Map<String, BasicAuthenticatorUser> userMap,
|
||||||
|
@JsonProperty("serializedUserMap") byte[] serializedUserMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.userMap = userMap;
|
||||||
|
this.serializedUserMap = serializedUserMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthenticatorUser> getUserMap()
|
||||||
|
{
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSerializedUserMap()
|
||||||
|
{
|
||||||
|
return serializedUserMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import io.druid.java.util.common.IAE;
|
||||||
|
import io.druid.security.basic.BasicAuthDBConfig;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.server.security.Access;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.AuthenticationResult;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@JsonTypeName("basic")
|
||||||
|
public class BasicRoleBasedAuthorizer implements Authorizer
|
||||||
|
{
|
||||||
|
private final BasicAuthorizerCacheManager cacheManager;
|
||||||
|
private final String name;
|
||||||
|
private final BasicAuthDBConfig dbConfig;
|
||||||
|
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicRoleBasedAuthorizer(
|
||||||
|
@JacksonInject BasicAuthorizerCacheManager cacheManager,
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
||||||
|
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
this.dbConfig = new BasicAuthDBConfig(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
enableCacheNotifications == null ? true : enableCacheNotifications,
|
||||||
|
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Access authorize(
|
||||||
|
AuthenticationResult authenticationResult, Resource resource, Action action
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (authenticationResult == null) {
|
||||||
|
throw new IAE("WTF? authenticationResult should never be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = cacheManager.getUserMap(name);
|
||||||
|
if (userMap == null) {
|
||||||
|
throw new IAE("Could not load userMap for authorizer [%s]", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = cacheManager.getRoleMap(name);
|
||||||
|
if (roleMap == null) {
|
||||||
|
throw new IAE("Could not load roleMap for authorizer [%s]", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
|
||||||
|
if (user == null) {
|
||||||
|
return new Access(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String roleName : user.getRoles()) {
|
||||||
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
|
for (BasicAuthorizerPermission permission : role.getPermissions()) {
|
||||||
|
if (permissionCheck(resource, action, permission)) {
|
||||||
|
return new Access(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Access(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean permissionCheck(Resource resource, Action action, BasicAuthorizerPermission permission)
|
||||||
|
{
|
||||||
|
if (action != permission.getResourceAction().getAction()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource permissionResource = permission.getResourceAction().getResource();
|
||||||
|
if (permissionResource.getType() != resource.getType()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern resourceNamePattern = permission.getResourceNamePattern();
|
||||||
|
Matcher resourceNameMatcher = resourceNamePattern.matcher(resource.getName());
|
||||||
|
return resourceNameMatcher.matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicAuthDBConfig getDbConfig()
|
||||||
|
{
|
||||||
|
return dbConfig;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is reponsible for maintaining a cache of the authorization database state. The BasicRBACAuthorizer
|
||||||
|
* uses an injected BasicAuthorizerCacheManager to make its authorization decisions.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthorizerCacheManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Update this cache manager's local state with fresh information pushed by the coordinator.
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix The name of the authorizer this update applies to.
|
||||||
|
* @param serializedUserAndRoleMap The updated, serialized user and role maps
|
||||||
|
*/
|
||||||
|
void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cache manager's local view of the user map for the authorizer named `authorizerPrefix`.
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix The name of the authorizer
|
||||||
|
* @return User map
|
||||||
|
*/
|
||||||
|
Map<String, BasicAuthorizerUser> getUserMap(String authorizerPrefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cache manager's local view of the role map for the authorizer named `authorizerPrefix`.
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix The name of the authorizer
|
||||||
|
* @return Role map
|
||||||
|
*/
|
||||||
|
Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix);
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification to druid services, containing updated authorizer user/role map state.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthorizerCacheNotifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Send the user map state contained in updatedUserMap to all non-coordinator Druid services
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix Name of authorizer being updated
|
||||||
|
* @param userAndRoleMap User/role map state
|
||||||
|
*/
|
||||||
|
void addUpdate(String authorizerPrefix, byte[] userAndRoleMap);
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import com.metamx.http.client.HttpClient;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.discovery.DruidNodeDiscoveryProvider;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.EscalatedClient;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.security.basic.BasicAuthDBConfig;
|
||||||
|
import io.druid.security.basic.CommonCacheNotifier;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerCacheNotifier
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG = new EmittingLogger(CoordinatorBasicAuthorizerCacheNotifier.class);
|
||||||
|
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
private CommonCacheNotifier cacheNotifier;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthorizerCacheNotifier(
|
||||||
|
AuthorizerMapper authorizerMapper,
|
||||||
|
DruidNodeDiscoveryProvider discoveryProvider,
|
||||||
|
@EscalatedClient HttpClient httpClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
cacheNotifier = new CommonCacheNotifier(
|
||||||
|
getAuthorizerConfigMap(authorizerMapper),
|
||||||
|
discoveryProvider,
|
||||||
|
httpClient,
|
||||||
|
"/druid-ext/basic-security/authorization/listen/%s",
|
||||||
|
"CoordinatorBasicAuthorizerCacheNotifier"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cacheNotifier.start();
|
||||||
|
lifecycleLock.started();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cacheNotifier.stop();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUpdate(String updatedAuthorizerPrefix, byte[] updatedUserMap)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
cacheNotifier.addUpdate(updatedAuthorizerPrefix, updatedUserMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BasicAuthDBConfig> getAuthorizerConfigMap(AuthorizerMapper mapper)
|
||||||
|
{
|
||||||
|
Preconditions.checkNotNull(mapper);
|
||||||
|
Preconditions.checkNotNull(mapper.getAuthorizerMap());
|
||||||
|
|
||||||
|
Map<String, BasicAuthDBConfig> authorizerConfigMap = new HashMap<>();
|
||||||
|
for (Map.Entry<String, Authorizer> entry : mapper.getAuthorizerMap().entrySet()) {
|
||||||
|
Authorizer authorizer = entry.getValue();
|
||||||
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
String authorizerName = entry.getKey();
|
||||||
|
BasicRoleBasedAuthorizer basicRoleBasedAuthorizer = (BasicRoleBasedAuthorizer) authorizer;
|
||||||
|
BasicAuthDBConfig dbConfig = basicRoleBasedAuthorizer.getDbConfig();
|
||||||
|
authorizerConfigMap.put(authorizerName, dbConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizerConfigMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import com.metamx.http.client.Request;
|
||||||
|
import io.druid.client.coordinator.Coordinator;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.discovery.DruidLeaderClient;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.RetryUtils;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.concurrent.Execs;
|
||||||
|
import io.druid.java.util.common.concurrent.ScheduledExecutors;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.authentication.BytesFullResponseHandler;
|
||||||
|
import io.druid.security.basic.authentication.BytesFullResponseHolder;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAuthorizerCacheManager
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG = new EmittingLogger(CoordinatorPollingBasicAuthorizerCacheManager.class);
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerUser>> cachedUserMaps;
|
||||||
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerRole>> cachedRoleMaps;
|
||||||
|
|
||||||
|
private final Set<String> authorizerPrefixes;
|
||||||
|
private final Injector injector;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
private final DruidLeaderClient druidLeaderClient;
|
||||||
|
private final BasicAuthCommonCacheConfig commonCacheConfig;
|
||||||
|
private final ScheduledExecutorService exec;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorPollingBasicAuthorizerCacheManager(
|
||||||
|
Injector injector,
|
||||||
|
BasicAuthCommonCacheConfig commonCacheConfig,
|
||||||
|
@Smile ObjectMapper objectMapper,
|
||||||
|
@Coordinator DruidLeaderClient druidLeaderClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.exec = Execs.scheduledSingleThreaded("CoordinatorPollingBasicAuthorizerCacheManager-Exec--%d");
|
||||||
|
this.injector = injector;
|
||||||
|
this.commonCacheConfig = commonCacheConfig;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
|
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
||||||
|
this.authorizerPrefixes = new HashSet<>();
|
||||||
|
this.druidLeaderClient = druidLeaderClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Starting CoordinatorPollingBasicAuthorizerCacheManager.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
initUserMaps();
|
||||||
|
|
||||||
|
ScheduledExecutors.scheduleWithFixedDelay(
|
||||||
|
exec,
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
|
||||||
|
LOG.debug("Inserting random polling delay of [%s] ms", randomDelay);
|
||||||
|
Thread.sleep(randomDelay);
|
||||||
|
|
||||||
|
LOG.debug("Scheduled cache poll is running");
|
||||||
|
for (String authorizerPrefix : authorizerPrefixes) {
|
||||||
|
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerPrefix, false);
|
||||||
|
if (userAndRoleMap != null) {
|
||||||
|
cachedUserMaps.put(authorizerPrefix, userAndRoleMap.getUserMap());
|
||||||
|
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Scheduled cache poll is done");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
lifecycleLock.started();
|
||||||
|
LOG.info("Started CoordinatorPollingBasicAuthorizerCacheManager.");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
throw new ISE("can't stop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("CoordinatorPollingBasicAuthorizerCacheManager is stopping.");
|
||||||
|
exec.shutdown();
|
||||||
|
LOG.info("CoordinatorPollingBasicAuthorizerCacheManager is stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
LOG.debug("Received cache update for authorizer [%s].", authorizerPrefix);
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
try {
|
||||||
|
UserAndRoleMap userAndRoleMap = objectMapper.readValue(
|
||||||
|
serializedUserAndRoleMap,
|
||||||
|
BasicAuthUtils.AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
|
||||||
|
cachedUserMaps.put(authorizerPrefix, userAndRoleMap.getUserMap());
|
||||||
|
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
||||||
|
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeMapToDisk(authorizerPrefix, serializedUserAndRoleMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "WTF? Could not deserialize user/role map received from coordinator.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerUser> getUserMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cachedUserMaps.get(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cachedRoleMaps.get(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserRoleMapFilename(String prefix)
|
||||||
|
{
|
||||||
|
return StringUtils.format("%s.authorizer.cache", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private UserAndRoleMap loadUserAndRoleMapFromDisk(String prefix) throws IOException
|
||||||
|
{
|
||||||
|
File userAndRoleMapFile = new File(commonCacheConfig.getCacheDirectory(), getUserRoleMapFilename(prefix));
|
||||||
|
if (!userAndRoleMapFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(
|
||||||
|
userAndRoleMapFile,
|
||||||
|
BasicAuthUtils.AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMapToDisk(String prefix, byte[] userMapBytes) throws IOException
|
||||||
|
{
|
||||||
|
File cacheDir = new File(commonCacheConfig.getCacheDirectory());
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
File userMapFile = new File(commonCacheConfig.getCacheDirectory(), getUserRoleMapFilename(prefix));
|
||||||
|
Files.write(userMapBytes, userMapFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private UserAndRoleMap fetchUserAndRoleMapFromCoordinator(String prefix, boolean isInit)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return RetryUtils.retry(
|
||||||
|
() -> {
|
||||||
|
return tryFetchMapsFromCoordinator(prefix);
|
||||||
|
},
|
||||||
|
e -> true,
|
||||||
|
commonCacheConfig.getMaxSyncRetries()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "Encountered exception while fetching user and role map for authorizer [%s]", prefix);
|
||||||
|
if (isInit) {
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
try {
|
||||||
|
LOG.info("Attempting to load user map snapshot from disk.");
|
||||||
|
return loadUserAndRoleMapFromDisk(prefix);
|
||||||
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
e2.addSuppressed(e);
|
||||||
|
LOG.makeAlert(e2, "Encountered exception while loading user-role map snapshot for authorizer [%s]", prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserAndRoleMap tryFetchMapsFromCoordinator(
|
||||||
|
String prefix
|
||||||
|
) throws Exception
|
||||||
|
{
|
||||||
|
Request req = druidLeaderClient.makeRequest(
|
||||||
|
HttpMethod.GET,
|
||||||
|
StringUtils.format("/druid-ext/basic-security/authorization/db/%s/cachedSerializedUserMap", prefix)
|
||||||
|
);
|
||||||
|
BytesFullResponseHolder responseHolder = (BytesFullResponseHolder) druidLeaderClient.go(
|
||||||
|
req,
|
||||||
|
new BytesFullResponseHandler()
|
||||||
|
);
|
||||||
|
byte[] userRoleMapBytes = responseHolder.getBytes();
|
||||||
|
|
||||||
|
UserAndRoleMap userAndRoleMap = objectMapper.readValue(
|
||||||
|
userRoleMapBytes,
|
||||||
|
BasicAuthUtils.AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
if (userAndRoleMap != null && commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeMapToDisk(prefix, userRoleMapBytes);
|
||||||
|
}
|
||||||
|
return userAndRoleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUserMaps()
|
||||||
|
{
|
||||||
|
AuthorizerMapper authorizerMapper = injector.getInstance(AuthorizerMapper.class);
|
||||||
|
|
||||||
|
if (authorizerMapper == null || authorizerMapper.getAuthorizerMap() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, Authorizer> entry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
||||||
|
Authorizer authorizer = entry.getValue();
|
||||||
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
String authorizerName = entry.getKey();
|
||||||
|
authorizerPrefixes.add(authorizerName);
|
||||||
|
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerName, true);
|
||||||
|
if (userAndRoleMap != null) {
|
||||||
|
cachedUserMaps.put(authorizerName, userAndRoleMap.getUserMap());
|
||||||
|
cachedRoleMaps.put(authorizerName, userAndRoleMap.getRoleMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MetadataStoragePollingBasicAuthorizerCacheManager implements BasicAuthorizerCacheManager
|
||||||
|
{
|
||||||
|
private final BasicAuthorizerMetadataStorageUpdater storageUpdater;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MetadataStoragePollingBasicAuthorizerCacheManager(
|
||||||
|
BasicAuthorizerMetadataStorageUpdater storageUpdater
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.storageUpdater = storageUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerUser> getUserMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return storageUpdater.getCachedUserMap(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return storageUpdater.getCachedRoleMap(authorizerPrefix);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.updater;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this interface are responsible for connecting directly to the metadata storage,
|
||||||
|
* modifying the authorizer database state or reading it. This interface is used by the
|
||||||
|
* MetadataStoragePollingBasicAuthorizerCacheManager (for reads) and the CoordinatorBasicAuthorizerResourceHandler
|
||||||
|
* (for handling configuration read/writes).
|
||||||
|
*/
|
||||||
|
public interface BasicAuthorizerMetadataStorageUpdater
|
||||||
|
{
|
||||||
|
void createUser(String prefix, String userName);
|
||||||
|
|
||||||
|
void deleteUser(String prefix, String userName);
|
||||||
|
|
||||||
|
void createRole(String prefix, String roleName);
|
||||||
|
|
||||||
|
void deleteRole(String prefix, String roleName);
|
||||||
|
|
||||||
|
void assignRole(String prefix, String userName, String roleName);
|
||||||
|
|
||||||
|
void unassignRole(String prefix, String userName, String roleName);
|
||||||
|
|
||||||
|
void setPermissions(String prefix, String roleName, List<ResourceAction> permissions);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> getCachedUserMap(String prefix);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix);
|
||||||
|
|
||||||
|
byte[] getCurrentUserMapBytes(String prefix);
|
||||||
|
|
||||||
|
byte[] getCurrentRoleMapBytes(String prefix);
|
||||||
|
|
||||||
|
void refreshAllNotification();
|
||||||
|
}
|
|
@ -0,0 +1,790 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.db.updater;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.metamx.emitter.EmittingLogger;
|
||||||
|
import io.druid.common.config.ConfigManager;
|
||||||
|
import io.druid.concurrent.LifecycleLock;
|
||||||
|
import io.druid.guice.ManageLifecycle;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.concurrent.Execs;
|
||||||
|
import io.druid.java.util.common.concurrent.ScheduledExecutors;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStart;
|
||||||
|
import io.druid.java.util.common.lifecycle.LifecycleStop;
|
||||||
|
import io.druid.metadata.MetadataCASUpdate;
|
||||||
|
import io.druid.metadata.MetadataStorageConnector;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRoleMapBundle;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUserMapBundle;
|
||||||
|
import io.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ManageLifecycle
|
||||||
|
public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAuthorizerMetadataStorageUpdater
|
||||||
|
{
|
||||||
|
private static final EmittingLogger LOG =
|
||||||
|
new EmittingLogger(CoordinatorBasicAuthorizerMetadataStorageUpdater.class);
|
||||||
|
|
||||||
|
private static final long UPDATE_RETRY_DELAY = 1000;
|
||||||
|
|
||||||
|
private static final String USERS = "users";
|
||||||
|
private static final String ROLES = "roles";
|
||||||
|
|
||||||
|
public static final List<ResourceAction> SUPERUSER_PERMISSIONS = makeSuperUserPermissions();
|
||||||
|
|
||||||
|
private final AuthorizerMapper authorizerMapper;
|
||||||
|
private final MetadataStorageConnector connector;
|
||||||
|
private final MetadataStorageTablesConfig connectorConfig;
|
||||||
|
private final BasicAuthorizerCacheNotifier cacheNotifier;
|
||||||
|
private final BasicAuthCommonCacheConfig commonCacheConfig;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final int numRetries = 5;
|
||||||
|
|
||||||
|
private final Map<String, BasicAuthorizerUserMapBundle> cachedUserMaps;
|
||||||
|
private final Map<String, BasicAuthorizerRoleMapBundle> cachedRoleMaps;
|
||||||
|
|
||||||
|
private final Set<String> authorizerNames;
|
||||||
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
|
|
||||||
|
private final ScheduledExecutorService exec;
|
||||||
|
private volatile boolean stopped = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
|
AuthorizerMapper authorizerMapper,
|
||||||
|
MetadataStorageConnector connector,
|
||||||
|
MetadataStorageTablesConfig connectorConfig,
|
||||||
|
BasicAuthCommonCacheConfig commonCacheConfig,
|
||||||
|
@Smile ObjectMapper objectMapper,
|
||||||
|
BasicAuthorizerCacheNotifier cacheNotifier,
|
||||||
|
ConfigManager configManager // ConfigManager creates the db table we need, set a dependency here
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.exec = Execs.scheduledSingleThreaded("CoordinatorBasicAuthorizerMetadataStorageUpdater-Exec--%d");
|
||||||
|
this.authorizerMapper = authorizerMapper;
|
||||||
|
this.connector = connector;
|
||||||
|
this.connectorConfig = connectorConfig;
|
||||||
|
this.commonCacheConfig = commonCacheConfig;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.cacheNotifier = cacheNotifier;
|
||||||
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
|
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
||||||
|
this.authorizerNames = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStart
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStart()) {
|
||||||
|
throw new ISE("can't start.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authorizerMapper == null || authorizerMapper.getAuthorizerMap() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOG.info("Starting CoordinatorBasicAuthorizerMetadataStorageUpdater");
|
||||||
|
for (Map.Entry<String, Authorizer> entry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
||||||
|
Authorizer authorizer = entry.getValue();
|
||||||
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
String authorizerName = entry.getKey();
|
||||||
|
authorizerNames.add(authorizerName);
|
||||||
|
BasicRoleBasedAuthorizer basicRoleBasedAuthorizer = (BasicRoleBasedAuthorizer) authorizer;
|
||||||
|
|
||||||
|
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
userMapBytes
|
||||||
|
);
|
||||||
|
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
||||||
|
|
||||||
|
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
roleMapBytes
|
||||||
|
);
|
||||||
|
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
||||||
|
|
||||||
|
initSuperusers(authorizerName, userMap, roleMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduledExecutors.scheduleWithFixedDelay(
|
||||||
|
exec,
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
|
new Callable<ScheduledExecutors.Signal>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ScheduledExecutors.Signal call() throws Exception
|
||||||
|
{
|
||||||
|
if (stopped) {
|
||||||
|
return ScheduledExecutors.Signal.STOP;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
LOG.debug("Scheduled db poll is running");
|
||||||
|
for (String authorizerName : authorizerNames) {
|
||||||
|
|
||||||
|
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
userMapBytes
|
||||||
|
);
|
||||||
|
if (userMapBytes != null) {
|
||||||
|
synchronized (cachedUserMaps) {
|
||||||
|
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
roleMapBytes
|
||||||
|
);
|
||||||
|
if (roleMapBytes != null) {
|
||||||
|
synchronized (cachedUserMaps) {
|
||||||
|
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Scheduled db poll is done");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
}
|
||||||
|
return ScheduledExecutors.Signal.REPEAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
lifecycleLock.started();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lifecycleLock.exitStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LifecycleStop
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if (!lifecycleLock.canStop()) {
|
||||||
|
throw new ISE("can't stop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("CoordinatorBasicAuthorizerMetadataStorageUpdater is stopping.");
|
||||||
|
stopped = true;
|
||||||
|
LOG.info("CoordinatorBasicAuthorizerMetadataStorageUpdater is stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String getPrefixedKeyColumn(String keyPrefix, String keyName)
|
||||||
|
{
|
||||||
|
return StringUtils.format("basic_authorization_%s_%s", keyPrefix, keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateUserMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthorizerUser> userMap,
|
||||||
|
byte[] oldUserMapValue,
|
||||||
|
byte[] newUserMapValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return tryUpdateUserAndRoleMap(prefix, userMap, oldUserMapValue, newUserMapValue, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateRoleMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap,
|
||||||
|
byte[] oldRoleMapValue,
|
||||||
|
byte[] newRoleMapValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return tryUpdateUserAndRoleMap(prefix, null, null, null, roleMap, oldRoleMapValue, newRoleMapValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateUserAndRoleMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthorizerUser> userMap,
|
||||||
|
byte[] oldUserMapValue,
|
||||||
|
byte[] newUserMapValue,
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap,
|
||||||
|
byte[] oldRoleMapValue,
|
||||||
|
byte[] newRoleMapValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
|
if (userMap != null) {
|
||||||
|
updates.add(
|
||||||
|
new MetadataCASUpdate(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, USERS),
|
||||||
|
oldUserMapValue,
|
||||||
|
newUserMapValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleMap != null) {
|
||||||
|
updates.add(
|
||||||
|
new MetadataCASUpdate(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, ROLES),
|
||||||
|
oldRoleMapValue,
|
||||||
|
newRoleMapValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
|
if (succeeded) {
|
||||||
|
if (userMap != null) {
|
||||||
|
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
|
||||||
|
}
|
||||||
|
if (roleMap != null) {
|
||||||
|
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdate(prefix, serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createUser(String prefix, String userName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
createUserInternal(prefix, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUser(String prefix, String userName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
deleteUserInternal(prefix, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createRole(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
createRoleInternal(prefix, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteRole(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
deleteRoleInternal(prefix, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignRole(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
assignRoleInternal(prefix, userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unassignRole(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
unassignRoleInternal(prefix, userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPermissions(
|
||||||
|
String prefix, String roleName, List<ResourceAction> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
setPermissionsInternal(prefix, roleName, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Map<String, BasicAuthorizerUser> getCachedUserMap(String prefix)
|
||||||
|
{
|
||||||
|
BasicAuthorizerUserMapBundle userMapBundle = cachedUserMaps.get(prefix);
|
||||||
|
return userMapBundle == null ? null : userMapBundle.getUserMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
|
||||||
|
{
|
||||||
|
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
|
||||||
|
return roleMapBundle == null ? null : roleMapBundle.getRoleMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCurrentUserMapBytes(String prefix)
|
||||||
|
{
|
||||||
|
return connector.lookup(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, USERS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCurrentRoleMapBytes(String prefix)
|
||||||
|
{
|
||||||
|
return connector.lookup(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, ROLES)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshAllNotification()
|
||||||
|
{
|
||||||
|
authorizerNames.forEach(
|
||||||
|
(authorizerName) -> {
|
||||||
|
try {
|
||||||
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(authorizerName);
|
||||||
|
cacheNotifier.addUpdate(authorizerName, serializedUserAndRoleMap);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getCurrentUserAndRoleMapSerialized(String prefix) throws IOException
|
||||||
|
{
|
||||||
|
BasicAuthorizerUserMapBundle userMapBundle = cachedUserMaps.get(prefix);
|
||||||
|
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
|
||||||
|
|
||||||
|
UserAndRoleMap userAndRoleMap = new UserAndRoleMap(
|
||||||
|
userMapBundle == null ? null : userMapBundle.getUserMap(),
|
||||||
|
roleMapBundle == null ? null : roleMapBundle.getRoleMap()
|
||||||
|
);
|
||||||
|
|
||||||
|
return objectMapper.writeValueAsBytes(userAndRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUserInternal(String prefix, String userName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (createUserOnce(prefix, userName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not create user[%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteUserInternal(String prefix, String userName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (deleteUserOnce(prefix, userName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not delete user[%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRoleInternal(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (createRoleOnce(prefix, roleName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not create role[%s] due to concurrent update contention.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteRoleInternal(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (deleteRoleOnce(prefix, roleName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not delete role[%s] due to concurrent update contention.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignRoleInternal(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (assignRoleOnce(prefix, userName, roleName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not assign role[%s] to user[%s] due to concurrent update contention.", roleName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unassignRoleInternal(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (unassignRoleOnce(prefix, userName, roleName)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not unassign role[%s] from user[%s] due to concurrent update contention.", roleName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (setPermissionsOnce(prefix, roleName, permissions)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ISE("Could not set permissions for role[%s] due to concurrent update contention.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createUserOnce(String prefix, String userName)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);
|
||||||
|
if (userMap.get(userName) != null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] already exists.", userName);
|
||||||
|
} else {
|
||||||
|
userMap.put(userName, new BasicAuthorizerUser(userName, null));
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteUserOnce(String prefix, String userName)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);
|
||||||
|
if (userMap.get(userName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
} else {
|
||||||
|
userMap.remove(userName);
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createRoleOnce(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(objectMapper, oldValue);
|
||||||
|
if (roleMap.get(roleName) != null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] already exists.", roleName);
|
||||||
|
} else {
|
||||||
|
roleMap.put(roleName, new BasicAuthorizerRole(roleName, null));
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
|
||||||
|
return tryUpdateRoleMap(prefix, roleMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteRoleOnce(String prefix, String roleName)
|
||||||
|
{
|
||||||
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
oldRoleMapValue
|
||||||
|
);
|
||||||
|
if (roleMap.get(roleName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
} else {
|
||||||
|
roleMap.remove(roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldUserMapValue
|
||||||
|
);
|
||||||
|
for (BasicAuthorizerUser user : userMap.values()) {
|
||||||
|
user.getRoles().remove(roleName);
|
||||||
|
}
|
||||||
|
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
|
||||||
|
|
||||||
|
return tryUpdateUserAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
userMap, oldUserMapValue, newUserMapValue,
|
||||||
|
roleMap, oldRoleMapValue, newRoleMapValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean assignRoleOnce(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
oldRoleMapValue
|
||||||
|
);
|
||||||
|
if (roleMap.get(roleName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldUserMapValue
|
||||||
|
);
|
||||||
|
BasicAuthorizerUser user = userMap.get(userName);
|
||||||
|
if (userMap.get(userName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.getRoles().contains(roleName)) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] already has role [%s].", userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.getRoles().add(roleName);
|
||||||
|
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
|
||||||
|
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
|
||||||
|
return tryUpdateUserAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
userMap, oldUserMapValue, newUserMapValue,
|
||||||
|
roleMap, oldRoleMapValue, oldRoleMapValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unassignRoleOnce(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
oldRoleMapValue
|
||||||
|
);
|
||||||
|
if (roleMap.get(roleName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
oldUserMapValue
|
||||||
|
);
|
||||||
|
BasicAuthorizerUser user = userMap.get(userName);
|
||||||
|
if (userMap.get(userName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.getRoles().contains(roleName)) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not have role [%s].", userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.getRoles().remove(roleName);
|
||||||
|
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
|
||||||
|
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
|
||||||
|
return tryUpdateUserAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
userMap, oldUserMapValue, newUserMapValue,
|
||||||
|
roleMap, oldRoleMapValue, oldRoleMapValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setPermissionsOnce(String prefix, String roleName, List<ResourceAction> permissions)
|
||||||
|
{
|
||||||
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
oldRoleMapValue
|
||||||
|
);
|
||||||
|
if (roleMap.get(roleName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
}
|
||||||
|
roleMap.put(
|
||||||
|
roleName,
|
||||||
|
new BasicAuthorizerRole(roleName, BasicAuthorizerPermission.makePermissionList(permissions))
|
||||||
|
);
|
||||||
|
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
|
||||||
|
|
||||||
|
return tryUpdateRoleMap(prefix, roleMap, oldRoleMapValue, newRoleMapValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSuperusers(
|
||||||
|
String authorizerName,
|
||||||
|
Map<String, BasicAuthorizerUser> userMap,
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!roleMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
||||||
|
createRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
|
||||||
|
setPermissionsInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, SUPERUSER_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roleMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
|
||||||
|
createRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
setPermissionsInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, SUPERUSER_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
|
||||||
|
createUserInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
assignRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
||||||
|
createUserInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
|
||||||
|
assignRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.ADMIN_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ResourceAction> makeSuperUserPermissions()
|
||||||
|
{
|
||||||
|
ResourceAction datasourceR = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
|
||||||
|
ResourceAction datasourceW = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
ResourceAction configR = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.CONFIG),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
|
||||||
|
ResourceAction configW = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.CONFIG),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
ResourceAction stateR = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.STATE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
|
||||||
|
ResourceAction stateW = new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.STATE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
return Lists.newArrayList(datasourceR, datasourceW, configR, configW, stateR, stateW);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,373 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.endpoint;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.sun.jersey.spi.container.ResourceFilters;
|
||||||
|
import io.druid.guice.LazySingleton;
|
||||||
|
import io.druid.security.basic.BasicSecurityResourceFilter;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Path("/druid-ext/basic-security/authorization")
|
||||||
|
@LazySingleton
|
||||||
|
public class BasicAuthorizerResource
|
||||||
|
{
|
||||||
|
private BasicAuthorizerResourceHandler resourceHandler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BasicAuthorizerResource(
|
||||||
|
BasicAuthorizerResourceHandler resourceHandler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.resourceHandler = resourceHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return Load status of authenticator DB caches
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/loadStatus")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getLoadStatus(
|
||||||
|
@Context HttpServletRequest req
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getLoadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* Sends an "update" notification to all services with the current user/role database state,
|
||||||
|
* causing them to refresh their DB cache state.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/refreshAll")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response refreshAll(
|
||||||
|
@Context HttpServletRequest req
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.refreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return List of all users
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/users")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getAllUsers(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getAllUsers(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user to retrieve information about
|
||||||
|
*
|
||||||
|
* @return Name, roles, and permissions of the user with userName, 400 error response if user doesn't exist
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("userName") final String userName,
|
||||||
|
@QueryParam("full") String full
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getUser(authorizerName, userName, full != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user with name userName
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name to assign the new user
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response createUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("userName") String userName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.createUser(authorizerName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user to delete
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user doesn't exist
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authorizerName}/users/{userName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response deleteUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("userName") String userName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.deleteUser(authorizerName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return List of all roles
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/roles")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getAllRoles(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getAllRoles(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get info about a role
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return Role name, users with role, and permissions of role. 400 error if role doesn't exist.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getRole(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("roleName") final String roleName,
|
||||||
|
@QueryParam("full") String full
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getRole(authorizerName, roleName, full != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new role.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response, 400 error if role already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response createRole(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("roleName") final String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.createRole(authorizerName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a role.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response, 400 error if role doesn't exist.
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authorizerName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response deleteRole(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.deleteRole(authorizerName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a role to a user.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if user/role don't exist, or if user already has the role
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/users/{userName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response assignRoleToUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("userName") String userName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.assignRoleToUser(authorizerName, userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a role from a user.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name of user
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if user/role don't exist, or if user does not have the role.
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authorizerName}/users/{userName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response unassignRoleFromUser(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("userName") String userName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.unassignRoleFromUser(authorizerName, userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the permissions of a role. This replaces the previous permissions of the role.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param roleName Name of role
|
||||||
|
* @param resourceActions Permissions to set
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if role doesn't exist.
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/roles/{roleName}/permissions")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response setRolePermissions(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("roleName") String roleName,
|
||||||
|
List<ResourceAction> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.setRolePermissions(authorizerName, roleName, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return serialized user map
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/cachedSerializedUserMap")
|
||||||
|
@Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getCachedSerializedUserMap(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getCachedMaps(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for update notifications for the auth storage
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param userName Name to assign the new user
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if user already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/listen/{authorizerName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response authorizerUpdateListener(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
byte[] serializedUserAndRoleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.authorizerUpdateListener(authorizerName, serializedUserAndRoleMap);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.endpoint;
|
||||||
|
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles authorizer-related API calls. Coordinator and non-coordinator methods are combined here because of an
|
||||||
|
* inability to selectively inject jetty resources in configure(Binder binder) of the extension module based
|
||||||
|
* on node type.
|
||||||
|
*/
|
||||||
|
public interface BasicAuthorizerResourceHandler
|
||||||
|
{
|
||||||
|
// coordinator methods
|
||||||
|
Response getAllUsers(String authorizerName);
|
||||||
|
|
||||||
|
Response getUser(String authorizerName, String userName, boolean isFull);
|
||||||
|
|
||||||
|
Response createUser(String authorizerName, String userName);
|
||||||
|
|
||||||
|
Response deleteUser(String authorizerName, String userName);
|
||||||
|
|
||||||
|
Response getAllRoles(String authorizerName);
|
||||||
|
|
||||||
|
Response getRole(String authorizerName, String roleName, boolean isFull);
|
||||||
|
|
||||||
|
Response createRole(String authorizerName, String roleName);
|
||||||
|
|
||||||
|
Response deleteRole(String authorizerName, String roleName);
|
||||||
|
|
||||||
|
Response assignRoleToUser(String authorizerName, String userName, String roleName);
|
||||||
|
|
||||||
|
Response unassignRoleFromUser(String authorizerName, String userName, String roleName);
|
||||||
|
|
||||||
|
Response setRolePermissions(String authorizerName, String roleName, List<ResourceAction> permissions);
|
||||||
|
|
||||||
|
Response getCachedMaps(String authorizerName);
|
||||||
|
|
||||||
|
Response refreshAll();
|
||||||
|
|
||||||
|
// non-coordinator methods
|
||||||
|
Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
// common
|
||||||
|
Response getLoadStatus();
|
||||||
|
}
|
|
@ -0,0 +1,431 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.endpoint;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.guice.annotations.Smile;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUserFull;
|
||||||
|
import io.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorizerResourceHandler
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(CoordinatorBasicAuthorizerResourceHandler.class);
|
||||||
|
|
||||||
|
private final BasicAuthorizerMetadataStorageUpdater storageUpdater;
|
||||||
|
private final Map<String, BasicRoleBasedAuthorizer> authorizerMap;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CoordinatorBasicAuthorizerResourceHandler(
|
||||||
|
BasicAuthorizerMetadataStorageUpdater storageUpdater,
|
||||||
|
AuthorizerMapper authorizerMapper,
|
||||||
|
@Smile ObjectMapper objectMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.storageUpdater = storageUpdater;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
|
||||||
|
this.authorizerMap = Maps.newHashMap();
|
||||||
|
for (Map.Entry<String, Authorizer> authorizerEntry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
||||||
|
final String authorizerName = authorizerEntry.getKey();
|
||||||
|
final Authorizer authorizer = authorizerEntry.getValue();
|
||||||
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
authorizerMap.put(
|
||||||
|
authorizerName,
|
||||||
|
(BasicRoleBasedAuthorizer) authorizer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllUsers(String authorizerName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
return Response.ok(userMap.keySet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getUser(String authorizerName, String userName, boolean isFull)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFull) {
|
||||||
|
return getUserFull(authorizerName, userName);
|
||||||
|
} else {
|
||||||
|
return getUserSimple(authorizerName, userName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createUser(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.createUser(authorizerName, userName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteUser(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.deleteUser(authorizerName, userName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllRoles(String authorizerName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response.ok(roleMap.keySet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getRole(String authorizerName, String roleName, boolean isFull)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFull) {
|
||||||
|
return getRoleFull(authorizerName, roleName);
|
||||||
|
} else {
|
||||||
|
return getRoleSimple(authorizerName, roleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createRole(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.createRole(authorizerName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteRole(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.deleteRole(authorizerName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response assignRoleToUser(String authorizerName, String userName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.assignRole(authorizerName, userName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response unassignRoleFromUser(String authorizerName, String userName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.unassignRole(authorizerName, userName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response setRolePermissions(
|
||||||
|
String authorizerName, String roleName, List<ResourceAction> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.setPermissions(authorizerName, roleName, permissions);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedMaps(String authorizerName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAndRoleMap userAndRoleMap = new UserAndRoleMap(
|
||||||
|
storageUpdater.getCachedUserMap(authorizerName),
|
||||||
|
storageUpdater.getCachedRoleMap(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response.ok(userAndRoleMap).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response refreshAll()
|
||||||
|
{
|
||||||
|
storageUpdater.refreshAllNotification();
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getLoadStatus()
|
||||||
|
{
|
||||||
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
|
authorizerMap.forEach(
|
||||||
|
(authorizerName, authorizer) -> {
|
||||||
|
loadStatus.put(authorizerName, storageUpdater.getCachedUserMap(authorizerName) != null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Response.ok(loadStatus).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response makeResponseForAuthorizerNotFound(String authorizerName)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error",
|
||||||
|
StringUtils.format("Basic authorizer with name [%s] does not exist.", authorizerName)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response makeResponseForBasicSecurityDBResourceException(BasicSecurityDBResourceException bsre)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error", bsre.getMessage()
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getUserSimple(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerUser user = userMap.get(userName);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
}
|
||||||
|
return Response.ok(user).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getUserFull(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerUser user = userMap.get(userName);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<BasicAuthorizerRole> roles = new HashSet<>();
|
||||||
|
for (String roleName : user.getRoles()) {
|
||||||
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
|
if (role == null) {
|
||||||
|
log.error("User [%s] had role [%s], but role was not found.", userName, roleName);
|
||||||
|
} else {
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerUserFull fullUser = new BasicAuthorizerUserFull(userName, roles);
|
||||||
|
return Response.ok(fullUser).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getRoleSimple(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
|
if (role == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
}
|
||||||
|
return Response.ok(role).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getRoleFull(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> users = new HashSet<>();
|
||||||
|
for (BasicAuthorizerUser user : userMap.values()) {
|
||||||
|
if (user.getRoles().contains(roleName)) {
|
||||||
|
users.add(user.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
|
if (role == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
|
}
|
||||||
|
BasicAuthorizerRoleFull roleFull = new BasicAuthorizerRoleFull(
|
||||||
|
roleName,
|
||||||
|
users,
|
||||||
|
role.getPermissions()
|
||||||
|
);
|
||||||
|
return Response.ok(roleFull).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.endpoint;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import io.druid.server.security.Authorizer;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerResourceHandler
|
||||||
|
{
|
||||||
|
private static final Logger log = new Logger(DefaultBasicAuthorizerResourceHandler.class);
|
||||||
|
private static final Response NOT_FOUND_RESPONSE = Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
|
||||||
|
private final BasicAuthorizerCacheManager cacheManager;
|
||||||
|
private final Map<String, BasicRoleBasedAuthorizer> authorizerMap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultBasicAuthorizerResourceHandler(
|
||||||
|
BasicAuthorizerCacheManager cacheManager,
|
||||||
|
AuthorizerMapper authorizerMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
|
||||||
|
this.authorizerMap = Maps.newHashMap();
|
||||||
|
for (Map.Entry<String, Authorizer> authorizerEntry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
||||||
|
final String authorizerName = authorizerEntry.getKey();
|
||||||
|
final Authorizer authorizer = authorizerEntry.getValue();
|
||||||
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
authorizerMap.put(
|
||||||
|
authorizerName,
|
||||||
|
(BasicRoleBasedAuthorizer) authorizer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllUsers(String authorizerName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getUser(String authorizerName, String userName, boolean isFull)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createUser(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteUser(String authorizerName, String userName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllRoles(String authorizerName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getRole(String authorizerName, String roleName, boolean isFull)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createRole(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteRole(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response assignRoleToUser(String authorizerName, String userName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response unassignRoleFromUser(String authorizerName, String userName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response setRolePermissions(
|
||||||
|
String authorizerName, String roleName, List<ResourceAction> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedMaps(String authorizerName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response refreshAll()
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
String errMsg = StringUtils.format("Received update for unknown authorizer[%s]", authorizerName);
|
||||||
|
log.error(errMsg);
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
"error",
|
||||||
|
StringUtils.format(errMsg)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheManager.handleAuthorizerUpdate(authorizerName, serializedUserAndRoleMap);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getLoadStatus()
|
||||||
|
{
|
||||||
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
|
authorizerMap.forEach(
|
||||||
|
(authorizerName, authorizer) -> {
|
||||||
|
loadStatus.put(authorizerName, cacheManager.getUserMap(authorizerName) != null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Response.ok(loadStatus).build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
public class BasicAuthorizerPermission
|
||||||
|
{
|
||||||
|
private final ResourceAction resourceAction;
|
||||||
|
private final Pattern resourceNamePattern;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerPermission(
|
||||||
|
@JsonProperty("resourceAction") ResourceAction resourceAction,
|
||||||
|
@JsonProperty("resourceNamePattern") Pattern resourceNamePattern
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.resourceAction = resourceAction;
|
||||||
|
this.resourceNamePattern = resourceNamePattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicAuthorizerPermission(
|
||||||
|
ResourceAction resourceAction
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.resourceAction = resourceAction;
|
||||||
|
try {
|
||||||
|
this.resourceNamePattern = Pattern.compile(resourceAction.getResource().getName());
|
||||||
|
}
|
||||||
|
catch (PatternSyntaxException pse) {
|
||||||
|
throw new BasicSecurityDBResourceException(
|
||||||
|
pse,
|
||||||
|
"Invalid permission, resource name regex[%s] does not compile.",
|
||||||
|
resourceAction.getResource().getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public ResourceAction getResourceAction()
|
||||||
|
{
|
||||||
|
return resourceAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Pattern getResourceNamePattern()
|
||||||
|
{
|
||||||
|
return resourceNamePattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BasicAuthorizerPermission> makePermissionList(List<ResourceAction> resourceActions)
|
||||||
|
{
|
||||||
|
List<BasicAuthorizerPermission> permissions = new ArrayList<>();
|
||||||
|
|
||||||
|
if (resourceActions == null) {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ResourceAction resourceAction : resourceActions) {
|
||||||
|
permissions.add(new BasicAuthorizerPermission(resourceAction));
|
||||||
|
}
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerPermission that = (BasicAuthorizerPermission) o;
|
||||||
|
|
||||||
|
if (getResourceAction() != null
|
||||||
|
? !getResourceAction().equals(that.getResourceAction())
|
||||||
|
: that.getResourceAction() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getResourceNamePattern() != null
|
||||||
|
? getResourceNamePattern().pattern().equals(that.getResourceNamePattern().pattern())
|
||||||
|
: that.getResourceNamePattern() == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getResourceAction() != null ? getResourceAction().hashCode() : 0;
|
||||||
|
result = 31 * result + (getResourceNamePattern().pattern() != null
|
||||||
|
? getResourceNamePattern().pattern().hashCode()
|
||||||
|
: 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BasicAuthorizerRole
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final List<BasicAuthorizerPermission> permissions;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerRole(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("permissions") List<BasicAuthorizerPermission> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.permissions = permissions == null ? new ArrayList<>() : permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public List<BasicAuthorizerPermission> getPermissions()
|
||||||
|
{
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerRole role = (BasicAuthorizerRole) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(role.getName()) : role.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getPermissions() != null ? getPermissions().equals(role.getPermissions()) : role.getPermissions() == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
|
result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BasicAuthorizerRoleFull
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final Set<String> users;
|
||||||
|
private final List<BasicAuthorizerPermission> permissions;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerRoleFull(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("users") Set<String> users,
|
||||||
|
@JsonProperty("permissions") List<BasicAuthorizerPermission> permissions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.users = users;
|
||||||
|
this.permissions = permissions == null ? new ArrayList<>() : permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public List<BasicAuthorizerPermission> getPermissions()
|
||||||
|
{
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Set<String> getUsers()
|
||||||
|
{
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerRoleFull that = (BasicAuthorizerRoleFull) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getUsers() != null ? !getUsers().equals(that.getUsers()) : that.getUsers() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getPermissions() != null ? getPermissions().equals(that.getPermissions()) : that.getPermissions() == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
|
result = 31 * result + (getUsers() != null ? getUsers().hashCode() : 0);
|
||||||
|
result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BasicAuthorizerRoleMapBundle
|
||||||
|
{
|
||||||
|
private final Map<String, BasicAuthorizerRole> roleMap;
|
||||||
|
private final byte[] serializedRoleMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerRoleMapBundle(
|
||||||
|
@JsonProperty("roleMap") Map<String, BasicAuthorizerRole> roleMap,
|
||||||
|
@JsonProperty("serializedRoleMap") byte[] serializedRoleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.roleMap = roleMap;
|
||||||
|
this.serializedRoleMap = serializedRoleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap()
|
||||||
|
{
|
||||||
|
return roleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSerializedRoleMap()
|
||||||
|
{
|
||||||
|
return serializedRoleMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BasicAuthorizerUser
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final Set<String> roles;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerUser(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("roles") Set<String> roles
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.roles = roles == null ? new HashSet<>() : roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Set<String> getRoles()
|
||||||
|
{
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerUser that = (BasicAuthorizerUser) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getRoles() != null ? getRoles().equals(that.getRoles()) : that.getRoles() == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
|
result = 31 * result + (getRoles() != null ? getRoles().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BasicAuthorizerUserFull
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final Set<BasicAuthorizerRole> roles;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerUserFull(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("roles") Set<BasicAuthorizerRole> roles
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.roles = roles == null ? new HashSet<>() : roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Set<BasicAuthorizerRole> getRoles()
|
||||||
|
{
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerUserFull that = (BasicAuthorizerUserFull) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getRoles() != null ? getRoles().equals(that.getRoles()) : that.getRoles() == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
|
result = 31 * result + (getRoles() != null ? getRoles().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BasicAuthorizerUserMapBundle
|
||||||
|
{
|
||||||
|
private final Map<String, BasicAuthorizerUser> userMap;
|
||||||
|
private final byte[] serializedUserMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerUserMapBundle(
|
||||||
|
@JsonProperty("userMap") Map<String, BasicAuthorizerUser> userMap,
|
||||||
|
@JsonProperty("serializedUserMap") byte[] serializedUserMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.userMap = userMap;
|
||||||
|
this.serializedUserMap = serializedUserMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerUser> getUserMap()
|
||||||
|
{
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSerializedUserMap()
|
||||||
|
{
|
||||||
|
return serializedUserMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class UserAndRoleMap
|
||||||
|
{
|
||||||
|
@JsonProperty
|
||||||
|
private Map<String, BasicAuthorizerUser> userMap;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private Map<String, BasicAuthorizerRole> roleMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public UserAndRoleMap(
|
||||||
|
@JsonProperty("userMap") Map<String, BasicAuthorizerUser> userMap,
|
||||||
|
@JsonProperty("roleMap") Map<String, BasicAuthorizerRole> roleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.userMap = userMap;
|
||||||
|
this.roleMap = roleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerUser> getUserMap()
|
||||||
|
{
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap()
|
||||||
|
{
|
||||||
|
return roleMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
io.druid.security.basic.BasicSecurityDruidModule
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security;
|
||||||
|
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class BasicAuthUtilsTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testHashPassword()
|
||||||
|
{
|
||||||
|
char[] password = "HELLO".toCharArray();
|
||||||
|
int iterations = BasicAuthUtils.DEFAULT_KEY_ITERATIONS;
|
||||||
|
byte[] salt = BasicAuthUtils.generateSalt();
|
||||||
|
byte[] hash = BasicAuthUtils.hashPassword(password, salt, iterations);
|
||||||
|
|
||||||
|
Assert.assertEquals(BasicAuthUtils.SALT_LENGTH, salt.length);
|
||||||
|
Assert.assertEquals(BasicAuthUtils.KEY_LENGTH / 8, hash.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authentication;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import io.druid.guice.GuiceInjectors;
|
||||||
|
import io.druid.guice.JsonConfigProvider;
|
||||||
|
import io.druid.guice.annotations.Self;
|
||||||
|
import io.druid.initialization.Initialization;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.metadata.TestDerbyConnector;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPEscalator;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.CoordinatorBasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.server.DruidNode;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
import io.druid.server.security.Escalator;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
|
{
|
||||||
|
private final static String AUTHENTICATOR_NAME = "test";
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
|
private TestDerbyConnector connector;
|
||||||
|
private MetadataStorageTablesConfig tablesConfig;
|
||||||
|
private CoordinatorBasicAuthenticatorMetadataStorageUpdater updater;
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
|
connector = derbyConnectorRule.getConnector();
|
||||||
|
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
updater = new CoordinatorBasicAuthenticatorMetadataStorageUpdater(
|
||||||
|
new AuthenticatorMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"test",
|
||||||
|
new BasicHTTPAuthenticator(
|
||||||
|
null,
|
||||||
|
"test",
|
||||||
|
"test",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
connector,
|
||||||
|
tablesConfig,
|
||||||
|
new BasicAuthCommonCacheConfig(null, null, null, null),
|
||||||
|
objectMapper,
|
||||||
|
new NoopBasicAuthenticatorCacheNotifier(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
updater.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createUser()
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of(
|
||||||
|
"druid", new BasicAuthenticatorUser("druid", null)
|
||||||
|
);
|
||||||
|
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
|
||||||
|
// create duplicate should fail
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] already exists.");
|
||||||
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteUser()
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
updater.deleteUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of();
|
||||||
|
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
|
||||||
|
// delete non-existent user should fail
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] does not exist.");
|
||||||
|
updater.deleteUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setCredentials()
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
updater.setUserCredentials(AUTHENTICATOR_NAME, "druid", new BasicAuthenticatorCredentialUpdate("helloworld", null));
|
||||||
|
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
||||||
|
);
|
||||||
|
BasicAuthenticatorCredentials credentials = userMap.get("druid").getCredentials();
|
||||||
|
|
||||||
|
byte[] recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
"helloworld".toCharArray(),
|
||||||
|
credentials.getSalt(),
|
||||||
|
credentials.getIterations()
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertArrayEquals(credentials.getHash(), recalculatedHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Injector setupInjector()
|
||||||
|
{
|
||||||
|
return Initialization.makeInjectorWithModules(
|
||||||
|
GuiceInjectors.makeStartupInjector(),
|
||||||
|
ImmutableList.<Module>of(
|
||||||
|
new Module()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
JsonConfigProvider.bindInstance(
|
||||||
|
binder,
|
||||||
|
Key.get(DruidNode.class, Self.class),
|
||||||
|
new DruidNode("test", "localhost", null, null, true, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
binder.bind(Escalator.class).toInstance(
|
||||||
|
new BasicHTTPEscalator(null, null, null)
|
||||||
|
);
|
||||||
|
|
||||||
|
binder.bind(AuthenticatorMapper.class).toInstance(
|
||||||
|
new AuthenticatorMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
"test",
|
||||||
|
new BasicHTTPAuthenticator(
|
||||||
|
null,
|
||||||
|
"test",
|
||||||
|
"test",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authentication;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import io.druid.guice.GuiceInjectors;
|
||||||
|
import io.druid.guice.JsonConfigProvider;
|
||||||
|
import io.druid.guice.annotations.Self;
|
||||||
|
import io.druid.initialization.Initialization;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.metadata.TestDerbyConnector;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
|
import io.druid.security.basic.authentication.BasicHTTPEscalator;
|
||||||
|
import io.druid.security.basic.authentication.db.updater.CoordinatorBasicAuthenticatorMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.BasicAuthenticatorResource;
|
||||||
|
import io.druid.security.basic.authentication.endpoint.CoordinatorBasicAuthenticatorResourceHandler;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import io.druid.server.DruidNode;
|
||||||
|
import io.druid.server.security.AuthenticatorMapper;
|
||||||
|
import io.druid.server.security.Escalator;
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
|
{
|
||||||
|
private final static String AUTHENTICATOR_NAME = "test";
|
||||||
|
private final static String AUTHENTICATOR_NAME2 = "test2";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
|
private TestDerbyConnector connector;
|
||||||
|
private MetadataStorageTablesConfig tablesConfig;
|
||||||
|
private BasicAuthenticatorResource resource;
|
||||||
|
private CoordinatorBasicAuthenticatorMetadataStorageUpdater storageUpdater;
|
||||||
|
private HttpServletRequest req;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
req = EasyMock.createStrictMock(HttpServletRequest.class);
|
||||||
|
|
||||||
|
connector = derbyConnectorRule.getConnector();
|
||||||
|
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
|
|
||||||
|
AuthenticatorMapper authenticatorMapper = new AuthenticatorMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
AUTHENTICATOR_NAME,
|
||||||
|
new BasicHTTPAuthenticator(
|
||||||
|
null,
|
||||||
|
AUTHENTICATOR_NAME,
|
||||||
|
"test",
|
||||||
|
"druid",
|
||||||
|
"druid",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
AUTHENTICATOR_NAME2,
|
||||||
|
new BasicHTTPAuthenticator(
|
||||||
|
null,
|
||||||
|
AUTHENTICATOR_NAME2,
|
||||||
|
"test",
|
||||||
|
"druid",
|
||||||
|
"druid",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
storageUpdater = new CoordinatorBasicAuthenticatorMetadataStorageUpdater(
|
||||||
|
authenticatorMapper,
|
||||||
|
connector,
|
||||||
|
tablesConfig,
|
||||||
|
new BasicAuthCommonCacheConfig(null, null, null, null),
|
||||||
|
objectMapper,
|
||||||
|
new NoopBasicAuthenticatorCacheNotifier(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
resource = new BasicAuthenticatorResource(
|
||||||
|
new CoordinatorBasicAuthenticatorResourceHandler(
|
||||||
|
storageUpdater,
|
||||||
|
authenticatorMapper,
|
||||||
|
objectMapper
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
storageUpdater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
storageUpdater.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidAuthenticator()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, "invalidName");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
errorMapWithMsg("Basic authenticator with name [invalidName] does not exist."),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAllUsers()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME), response.getEntity());
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid2");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid3");
|
||||||
|
|
||||||
|
Set<String> expectedUsers = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid",
|
||||||
|
"druid2",
|
||||||
|
"druid3"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSeparateDatabaseTables()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME), response.getEntity());
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid2");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME, "druid3");
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME2, "druid4");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME2, "druid5");
|
||||||
|
resource.createUser(req, AUTHENTICATOR_NAME2, "druid6");
|
||||||
|
|
||||||
|
Set<String> expectedUsers = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid",
|
||||||
|
"druid2",
|
||||||
|
"druid3"
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> expectedUsers2 = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid4",
|
||||||
|
"druid5",
|
||||||
|
"druid6"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHENTICATOR_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers2, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteUser()
|
||||||
|
{
|
||||||
|
Response response = resource.createUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthenticatorUser expectedUser = new BasicAuthenticatorUser("druid", null);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserCredentials()
|
||||||
|
{
|
||||||
|
Response response = resource.createUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.updateUserCredentials(
|
||||||
|
req,
|
||||||
|
AUTHENTICATOR_NAME,
|
||||||
|
"druid",
|
||||||
|
new BasicAuthenticatorCredentialUpdate("helloworld", null)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthenticatorUser actualUser = (BasicAuthenticatorUser) response.getEntity();
|
||||||
|
Assert.assertEquals("druid", actualUser.getName());
|
||||||
|
BasicAuthenticatorCredentials credentials = actualUser.getCredentials();
|
||||||
|
|
||||||
|
byte[] salt = credentials.getSalt();
|
||||||
|
byte[] hash = credentials.getHash();
|
||||||
|
int iterations = credentials.getIterations();
|
||||||
|
Assert.assertEquals(BasicAuthUtils.SALT_LENGTH, salt.length);
|
||||||
|
Assert.assertEquals(BasicAuthUtils.KEY_LENGTH / 8, hash.length);
|
||||||
|
Assert.assertEquals(BasicAuthUtils.DEFAULT_KEY_ITERATIONS, iterations);
|
||||||
|
|
||||||
|
byte[] recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
"helloworld".toCharArray(),
|
||||||
|
salt,
|
||||||
|
iterations
|
||||||
|
);
|
||||||
|
Assert.assertArrayEquals(recalculatedHash, hash);
|
||||||
|
|
||||||
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.updateUserCredentials(
|
||||||
|
req,
|
||||||
|
AUTHENTICATOR_NAME,
|
||||||
|
"druid",
|
||||||
|
new BasicAuthenticatorCredentialUpdate("helloworld", null)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> errorMapWithMsg(String errorMsg)
|
||||||
|
{
|
||||||
|
return ImmutableMap.of("error", errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Injector setupInjector()
|
||||||
|
{
|
||||||
|
return Initialization.makeInjectorWithModules(
|
||||||
|
GuiceInjectors.makeStartupInjector(),
|
||||||
|
ImmutableList.<Module>of(
|
||||||
|
new Module()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void configure(Binder binder)
|
||||||
|
{
|
||||||
|
JsonConfigProvider.bindInstance(
|
||||||
|
binder,
|
||||||
|
Key.get(DruidNode.class, Self.class),
|
||||||
|
new DruidNode("test", "localhost", null, null, true, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
binder.bind(Escalator.class).toInstance(
|
||||||
|
new BasicHTTPEscalator(null, null, null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authentication;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheNotifier;
|
||||||
|
|
||||||
|
public class NoopBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void addUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.metadata.TestDerbyConnector;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.cache.MetadataStoragePollingBasicAuthorizerCacheManager;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.server.security.Access;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.AuthenticationResult;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BasicRoleBasedAuthorizerTest
|
||||||
|
{
|
||||||
|
private static final String AUTHORIZER_NAME = "test";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
|
private BasicRoleBasedAuthorizer authorizer;
|
||||||
|
private TestDerbyConnector connector;
|
||||||
|
private MetadataStorageTablesConfig tablesConfig;
|
||||||
|
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
connector = derbyConnectorRule.getConnector();
|
||||||
|
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
|
new AuthorizerMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
connector,
|
||||||
|
tablesConfig,
|
||||||
|
new BasicAuthCommonCacheConfig(null, null, null, null),
|
||||||
|
new ObjectMapper(new SmileFactory()),
|
||||||
|
new NoopBasicAuthorizerCacheNotifier(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.start();
|
||||||
|
|
||||||
|
authorizer = new BasicRoleBasedAuthorizer(
|
||||||
|
new MetadataStoragePollingBasicAuthorizerCacheManager(
|
||||||
|
updater
|
||||||
|
),
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuth()
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> permissions = Lists.newArrayList(
|
||||||
|
new ResourceAction(
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(AUTHORIZER_NAME, "druidRole", permissions);
|
||||||
|
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null);
|
||||||
|
|
||||||
|
Access access = authorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = authorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,377 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.metadata.TestDerbyConnector;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
|
{
|
||||||
|
private final static String AUTHORIZER_NAME = "test";
|
||||||
|
|
||||||
|
private final static Map<String, BasicAuthorizerUser> BASE_USER_MAP = ImmutableMap.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
new BasicAuthorizerUser(BasicAuthUtils.ADMIN_NAME, ImmutableSet.of(BasicAuthUtils.ADMIN_NAME)),
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
new BasicAuthorizerUser(BasicAuthUtils.INTERNAL_USER_NAME, ImmutableSet.of(
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME))
|
||||||
|
);
|
||||||
|
|
||||||
|
private final static Map<String, BasicAuthorizerRole> BASE_ROLE_MAP = ImmutableMap.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
new BasicAuthorizerRole(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthorizerPermission.makePermissionList(CoordinatorBasicAuthorizerMetadataStorageUpdater.SUPERUSER_PERMISSIONS)
|
||||||
|
),
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
new BasicAuthorizerRole(
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
BasicAuthorizerPermission.makePermissionList(CoordinatorBasicAuthorizerMetadataStorageUpdater.SUPERUSER_PERMISSIONS)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
|
private TestDerbyConnector connector;
|
||||||
|
private MetadataStorageTablesConfig tablesConfig;
|
||||||
|
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
|
connector = derbyConnectorRule.getConnector();
|
||||||
|
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
|
new AuthorizerMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
connector,
|
||||||
|
tablesConfig,
|
||||||
|
new BasicAuthCommonCacheConfig(null, null, null, null),
|
||||||
|
objectMapper,
|
||||||
|
new NoopBasicAuthorizerCacheNotifier(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// user tests
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteUser() throws Exception
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
Map<String, BasicAuthorizerUser> expectedUserMap = Maps.newHashMap(BASE_USER_MAP);
|
||||||
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of()));
|
||||||
|
Map<String, BasicAuthorizerUser> actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
|
||||||
|
updater.deleteUser(AUTHORIZER_NAME, "druid");
|
||||||
|
expectedUserMap.remove("druid");
|
||||||
|
actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteNonExistentUser() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] does not exist.");
|
||||||
|
updater.deleteUser(AUTHORIZER_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDuplicateUser() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] already exists.");
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// role tests
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteRole() throws Exception
|
||||||
|
{
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = Maps.newHashMap(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put("druid", new BasicAuthorizerRole("druid", ImmutableList.of()));
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.deleteRole(AUTHORIZER_NAME, "druid");
|
||||||
|
expectedRoleMap.remove("druid");
|
||||||
|
actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteNonExistentRole() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Role [druid] does not exist.");
|
||||||
|
updater.deleteRole(AUTHORIZER_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDuplicateRole() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Role [druid] already exists.");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// role and user tests
|
||||||
|
@Test
|
||||||
|
public void testAddAndRemoveRole() throws Exception
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> expectedUserMap = Maps.newHashMap(BASE_USER_MAP);
|
||||||
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of("druidRole")));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = Maps.newHashMap(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put("druidRole", new BasicAuthorizerRole("druidRole", ImmutableList.of()));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.unassignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of()));
|
||||||
|
actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRoleToNonExistentUser() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [nonUser] does not exist.");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "nonUser", "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddNonexistentRoleToUser() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Role [nonRole] does not exist.");
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "nonRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddExistingRoleToUserFails() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] already has role [druidRole].");
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnassignInvalidRoleAssignmentFails() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("User [druid] does not have role [druidRole].");
|
||||||
|
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> expectedUserMap = Maps.newHashMap(BASE_USER_MAP);
|
||||||
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of()));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = Maps.newHashMap(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put("druidRole", new BasicAuthorizerRole("druidRole", ImmutableList.of()));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.unassignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
// role and permission tests
|
||||||
|
@Test
|
||||||
|
public void testSetRolePermissions() throws Exception
|
||||||
|
{
|
||||||
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> permsToAdd = ImmutableList.of(
|
||||||
|
new ResourceAction(
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(AUTHORIZER_NAME, "druidRole", permsToAdd);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> expectedUserMap = Maps.newHashMap(BASE_USER_MAP);
|
||||||
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of("druidRole")));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = Maps.newHashMap(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put(
|
||||||
|
"druidRole",
|
||||||
|
new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(permsToAdd))
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentUserMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.setPermissions(AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
expectedRoleMap.put("druidRole", new BasicAuthorizerRole("druidRole", null));
|
||||||
|
actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddPermissionToNonExistentRole() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Role [druidRole] does not exist.");
|
||||||
|
|
||||||
|
List<ResourceAction> permsToAdd = ImmutableList.of(
|
||||||
|
new ResourceAction(
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(AUTHORIZER_NAME, "druidRole", permsToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddBadPermission() throws Exception
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Invalid permission, resource name regex[??????????] does not compile.");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> permsToAdd = ImmutableList.of(
|
||||||
|
new ResourceAction(
|
||||||
|
new Resource("??????????", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(AUTHORIZER_NAME, "druidRole", permsToAdd);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,588 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import io.druid.metadata.MetadataStorageTablesConfig;
|
||||||
|
import io.druid.metadata.TestDerbyConnector;
|
||||||
|
import io.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import io.druid.security.basic.BasicAuthUtils;
|
||||||
|
import io.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import io.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.BasicAuthorizerResource;
|
||||||
|
import io.druid.security.basic.authorization.endpoint.CoordinatorBasicAuthorizerResourceHandler;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import io.druid.security.basic.authorization.entity.BasicAuthorizerUserFull;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.AuthorizerMapper;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CoordinatorBasicAuthorizerResourceTest
|
||||||
|
{
|
||||||
|
private final static String AUTHORIZER_NAME = "test";
|
||||||
|
private final static String AUTHORIZER_NAME2 = "test2";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
|
private TestDerbyConnector connector;
|
||||||
|
private MetadataStorageTablesConfig tablesConfig;
|
||||||
|
private BasicAuthorizerResource resource;
|
||||||
|
private CoordinatorBasicAuthorizerMetadataStorageUpdater storageUpdater;
|
||||||
|
private HttpServletRequest req;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
req = EasyMock.createStrictMock(HttpServletRequest.class);
|
||||||
|
|
||||||
|
connector = derbyConnectorRule.getConnector();
|
||||||
|
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
AuthorizerMapper authorizerMapper = new AuthorizerMapper(
|
||||||
|
ImmutableMap.of(
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
AUTHORIZER_NAME2,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
AUTHORIZER_NAME2,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
storageUpdater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
|
authorizerMapper,
|
||||||
|
connector,
|
||||||
|
tablesConfig,
|
||||||
|
new BasicAuthCommonCacheConfig(null, null, null, null),
|
||||||
|
new ObjectMapper(new SmileFactory()),
|
||||||
|
new NoopBasicAuthorizerCacheNotifier(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
resource = new BasicAuthorizerResource(
|
||||||
|
new CoordinatorBasicAuthorizerResourceHandler(
|
||||||
|
storageUpdater,
|
||||||
|
authorizerMapper,
|
||||||
|
new ObjectMapper(new SmileFactory())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
storageUpdater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
storageUpdater.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSeparateDatabaseTables()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid3");
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME2, "druid4");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME2, "druid5");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME2, "druid6");
|
||||||
|
|
||||||
|
Set<String> expectedUsers = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid",
|
||||||
|
"druid2",
|
||||||
|
"druid3"
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> expectedUsers2 = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid4",
|
||||||
|
"druid5",
|
||||||
|
"druid6"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHORIZER_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers2, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidAuthorizer()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, "invalidName");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
errorMapWithMsg("Basic authorizer with name [invalidName] does not exist."),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAllUsers()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllUsers(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
|
resource.createUser(req, AUTHORIZER_NAME, "druid3");
|
||||||
|
|
||||||
|
Set<String> expectedUsers = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid",
|
||||||
|
"druid2",
|
||||||
|
"druid3"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAllRoles()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllRoles(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
resource.createRole(req, AUTHORIZER_NAME, "druid");
|
||||||
|
resource.createRole(req, AUTHORIZER_NAME, "druid2");
|
||||||
|
resource.createRole(req, AUTHORIZER_NAME, "druid3");
|
||||||
|
|
||||||
|
Set<String> expectedRoles = ImmutableSet.of(
|
||||||
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
BasicAuthUtils.INTERNAL_USER_NAME,
|
||||||
|
"druid",
|
||||||
|
"druid2",
|
||||||
|
"druid3"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllRoles(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRoles, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteUser()
|
||||||
|
{
|
||||||
|
Response response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerUser expectedUser = new BasicAuthorizerUser(
|
||||||
|
"druid",
|
||||||
|
ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.deleteUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.deleteUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteRole()
|
||||||
|
{
|
||||||
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.deleteRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.deleteRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("Role [druidRole] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("Role [druidRole] does not exist."), response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoleAssignment() throws Exception
|
||||||
|
{
|
||||||
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerUser expectedUser = new BasicAuthorizerUser(
|
||||||
|
"druid",
|
||||||
|
ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedUser = new BasicAuthorizerUser(
|
||||||
|
"druid",
|
||||||
|
ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteAssignedRole()
|
||||||
|
{
|
||||||
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerUser expectedUser = new BasicAuthorizerUser(
|
||||||
|
"druid",
|
||||||
|
ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerUser expectedUser2 = new BasicAuthorizerUser(
|
||||||
|
"druid2",
|
||||||
|
ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser2, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.deleteRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedUser = new BasicAuthorizerUser(
|
||||||
|
"druid",
|
||||||
|
ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedUser2 = new BasicAuthorizerUser(
|
||||||
|
"druid2",
|
||||||
|
ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedUser2, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRolesAndPerms()
|
||||||
|
{
|
||||||
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
List<ResourceAction> perms = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("A", ResourceType.DATASOURCE), Action.READ),
|
||||||
|
new ResourceAction(new Resource("B", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("C", ResourceType.CONFIG), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", perms);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "wrongRole", perms);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("Role [wrongRole] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms));
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
List<ResourceAction> newPerms = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("D", ResourceType.DATASOURCE), Action.READ),
|
||||||
|
new ResourceAction(new Resource("B", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("F", ResourceType.CONFIG), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", newPerms);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(newPerms));
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedRole = new BasicAuthorizerRole("druidRole", null);
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUsersRolesAndPerms()
|
||||||
|
{
|
||||||
|
Response response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createRole(req, AUTHORIZER_NAME, "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
List<ResourceAction> perms = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("A", ResourceType.DATASOURCE), Action.READ),
|
||||||
|
new ResourceAction(new Resource("B", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("C", ResourceType.CONFIG), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ResourceAction> perms2 = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("D", ResourceType.STATE), Action.READ),
|
||||||
|
new ResourceAction(new Resource("E", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("F", ResourceType.CONFIG), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", perms);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole2", perms2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms));
|
||||||
|
BasicAuthorizerRole expectedRole2 = new BasicAuthorizerRole("druidRole2", BasicAuthorizerPermission.makePermissionList(perms2));
|
||||||
|
Set<BasicAuthorizerRole> expectedRoles = Sets.newHashSet(expectedRole, expectedRole2);
|
||||||
|
|
||||||
|
BasicAuthorizerUserFull expectedUserFull = new BasicAuthorizerUserFull("druid", expectedRoles);
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull, response.getEntity());
|
||||||
|
|
||||||
|
BasicAuthorizerUserFull expectedUserFull2 = new BasicAuthorizerUserFull("druid2", expectedRoles);
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull2, response.getEntity());
|
||||||
|
|
||||||
|
Set<String> expectedUserSet = Sets.newHashSet("druid", "druid2");
|
||||||
|
BasicAuthorizerRoleFull expectedRoleFull = new BasicAuthorizerRoleFull(
|
||||||
|
"druidRole",
|
||||||
|
expectedUserSet,
|
||||||
|
BasicAuthorizerPermission.makePermissionList(perms)
|
||||||
|
);
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRoleFull, response.getEntity());
|
||||||
|
|
||||||
|
BasicAuthorizerRoleFull expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
||||||
|
"druidRole2",
|
||||||
|
expectedUserSet,
|
||||||
|
BasicAuthorizerPermission.makePermissionList(perms2)
|
||||||
|
);
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRoleFull2, response.getEntity());
|
||||||
|
|
||||||
|
|
||||||
|
perms = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("A", ResourceType.DATASOURCE), Action.READ),
|
||||||
|
new ResourceAction(new Resource("C", ResourceType.CONFIG), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
perms2 = ImmutableList.of(
|
||||||
|
new ResourceAction(new Resource("E", ResourceType.DATASOURCE), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", perms);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole2", perms2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms));
|
||||||
|
expectedRole2 = new BasicAuthorizerRole("druidRole2", BasicAuthorizerPermission.makePermissionList(perms2));
|
||||||
|
expectedRoles = Sets.newHashSet(expectedRole, expectedRole2);
|
||||||
|
expectedUserFull = new BasicAuthorizerUserFull("druid", expectedRoles);
|
||||||
|
expectedUserFull2 = new BasicAuthorizerUserFull("druid2", expectedRoles);
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull2, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
|
||||||
|
expectedUserFull = new BasicAuthorizerUserFull("druid", Sets.newHashSet(expectedRole2));
|
||||||
|
expectedUserFull2 = new BasicAuthorizerUserFull("druid2", Sets.newHashSet(expectedRole));
|
||||||
|
expectedRoleFull = new BasicAuthorizerRoleFull(
|
||||||
|
"druidRole",
|
||||||
|
Sets.newHashSet("druid2"),
|
||||||
|
BasicAuthorizerPermission.makePermissionList(perms)
|
||||||
|
);
|
||||||
|
expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
||||||
|
"druidRole2",
|
||||||
|
Sets.newHashSet("druid"),
|
||||||
|
BasicAuthorizerPermission.makePermissionList(perms2)
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedUserFull2, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRoleFull, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRoleFull2, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> errorMapWithMsg(String errorMsg)
|
||||||
|
{
|
||||||
|
return ImmutableMap.of("error", errorMsg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.security.authorization;
|
||||||
|
|
||||||
|
import io.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
|
||||||
|
|
||||||
|
public class NoopBasicAuthorizerCacheNotifier implements BasicAuthorizerCacheNotifier
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void addUpdate(String authorizerPrefix, byte[] userAndRoleMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,5 +41,4 @@ public interface PollingCache<K, V>
|
||||||
* close and clean the resources used by the cache
|
* close and clean the resources used by the cache
|
||||||
*/
|
*/
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,19 @@ command=java
|
||||||
-Ddruid.broker.cache.populateCache=true
|
-Ddruid.broker.cache.populateCache=true
|
||||||
-Ddruid.cache.type=local
|
-Ddruid.cache.type=local
|
||||||
-Ddruid.cache.sizeInBytes=40000000
|
-Ddruid.cache.sizeInBytes=40000000
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/broker
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server broker
|
io.druid.cli.Main server broker
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -15,6 +15,19 @@ command=java
|
||||||
-Ddruid.metadata.storage.connector.password=diurd
|
-Ddruid.metadata.storage.connector.password=diurd
|
||||||
-Ddruid.zk.service.host=druid-zookeeper-kafka
|
-Ddruid.zk.service.host=druid-zookeeper-kafka
|
||||||
-Ddruid.coordinator.startDelay=PT5S
|
-Ddruid.coordinator.startDelay=PT5S
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/coordinator
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server coordinator
|
io.druid.cli.Main server coordinator
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -19,6 +19,19 @@ command=java
|
||||||
-Ddruid.server.http.numThreads=100
|
-Ddruid.server.http.numThreads=100
|
||||||
-Ddruid.segmentCache.locations="[{\"path\":\"/shared/druid/indexCache\",\"maxSize\":5000000000}]"
|
-Ddruid.segmentCache.locations="[{\"path\":\"/shared/druid/indexCache\",\"maxSize\":5000000000}]"
|
||||||
-Ddruid.server.maxSize=5000000000
|
-Ddruid.server.maxSize=5000000000
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/historical
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server historical
|
io.druid.cli.Main server historical
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -22,6 +22,19 @@ command=java
|
||||||
-Ddruid.worker.ip=%(ENV_HOST_IP)s
|
-Ddruid.worker.ip=%(ENV_HOST_IP)s
|
||||||
-Ddruid.selectors.indexing.serviceName=druid/overlord
|
-Ddruid.selectors.indexing.serviceName=druid/overlord
|
||||||
-Ddruid.indexer.task.chathandler.type=announce
|
-Ddruid.indexer.task.chathandler.type=announce
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/middleManager
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server middleManager
|
io.druid.cli.Main server middleManager
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -17,6 +17,19 @@ command=java
|
||||||
-Ddruid.indexer.storage.type=metadata
|
-Ddruid.indexer.storage.type=metadata
|
||||||
-Ddruid.indexer.logs.directory=/shared/tasklogs
|
-Ddruid.indexer.logs.directory=/shared/tasklogs
|
||||||
-Ddruid.indexer.runner.type=remote
|
-Ddruid.indexer.runner.type=remote
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/overlord
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server overlord
|
io.druid.cli.Main server overlord
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -10,6 +10,19 @@ command=java
|
||||||
-Ddruid.host=%(ENV_HOST_IP)s
|
-Ddruid.host=%(ENV_HOST_IP)s
|
||||||
-Ddruid.zk.service.host=druid-zookeeper-kafka
|
-Ddruid.zk.service.host=druid-zookeeper-kafka
|
||||||
-Ddruid.server.http.numThreads=100
|
-Ddruid.server.http.numThreads=100
|
||||||
|
-Ddruid.lookup.numLookupLoadingThreads=1
|
||||||
|
-Ddruid.auth.authenticatorChain="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authenticator.basic.type=basic
|
||||||
|
-Ddruid.auth.authenticator.basic.initialAdminPassword=priest
|
||||||
|
-Ddruid.auth.authenticator.basic.initialInternalClientPassword=warlock
|
||||||
|
-Ddruid.auth.authenticator.basic.authorizerName=basic
|
||||||
|
-Ddruid.auth.basic.common.cacheDirectory=/tmp/authCache/router
|
||||||
|
-Ddruid.escalator.type=basic
|
||||||
|
-Ddruid.escalator.internalClientUsername=druid_system
|
||||||
|
-Ddruid.escalator.internalClientPassword=warlock
|
||||||
|
-Ddruid.escalator.authorizerName=basic
|
||||||
|
-Ddruid.auth.authorizers="[\"basic\"]"
|
||||||
|
-Ddruid.auth.authorizer.basic.type=basic
|
||||||
-cp /shared/docker/lib/*
|
-cp /shared/docker/lib/*
|
||||||
io.druid.cli.Main server router
|
io.druid.cli.Main server router
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
|
@ -81,6 +81,11 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.druid.extensions</groupId>
|
||||||
|
<artifactId>druid-basic-security</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.druid</groupId>
|
<groupId>io.druid</groupId>
|
||||||
<artifactId>druid-services</artifactId>
|
<artifactId>druid-services</artifactId>
|
||||||
|
|
|
@ -40,7 +40,7 @@ docker run -d --privileged --name druid-overlord -p 8090:8090 -v $SHARED_DIR:/sh
|
||||||
docker run -d --privileged --name druid-coordinator -p 8081:8081 -v $SHARED_DIR:/shared -v $DOCKERDIR/coordinator.conf:$SUPERVISORDIR/coordinator.conf --link druid-overlord:druid-overlord --link druid-metadata-storage:druid-metadata-storage --link druid-zookeeper-kafka:druid-zookeeper-kafka druid/cluster
|
docker run -d --privileged --name druid-coordinator -p 8081:8081 -v $SHARED_DIR:/shared -v $DOCKERDIR/coordinator.conf:$SUPERVISORDIR/coordinator.conf --link druid-overlord:druid-overlord --link druid-metadata-storage:druid-metadata-storage --link druid-zookeeper-kafka:druid-zookeeper-kafka druid/cluster
|
||||||
|
|
||||||
# Start Historical
|
# Start Historical
|
||||||
docker run -d --privileged --name druid-historical -v $SHARED_DIR:/shared -v $DOCKERDIR/historical.conf:$SUPERVISORDIR/historical.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka druid/cluster
|
docker run -d --privileged --name druid-historical -p 8083:8083 -v $SHARED_DIR:/shared -v $DOCKERDIR/historical.conf:$SUPERVISORDIR/historical.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka druid/cluster
|
||||||
|
|
||||||
# Start Middlemanger
|
# Start Middlemanger
|
||||||
docker run -d --privileged --name druid-middlemanager -p 8091:8091 -p 8100:8100 -p 8101:8101 -p 8102:8102 -p 8103:8103 -p 8104:8104 -p 8105:8105 -v $RESOURCEDIR:/resources -v $SHARED_DIR:/shared -v $DOCKERDIR/middlemanager.conf:$SUPERVISORDIR/middlemanager.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka --link druid-overlord:druid-overlord druid/cluster
|
docker run -d --privileged --name druid-middlemanager -p 8091:8091 -p 8100:8100 -p 8101:8101 -p 8102:8102 -p 8103:8103 -p 8104:8104 -p 8105:8105 -v $RESOURCEDIR:/resources -v $SHARED_DIR:/shared -v $DOCKERDIR/middlemanager.conf:$SUPERVISORDIR/middlemanager.conf --link druid-zookeeper-kafka:druid-zookeeper-kafka --link druid-overlord:druid-overlord druid/cluster
|
||||||
|
|
|
@ -138,6 +138,12 @@ public class ConfigFileConfigProvider implements IntegrationTestingConfigProvide
|
||||||
return brokerUrl;
|
return brokerUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHistoricalUrl()
|
||||||
|
{
|
||||||
|
return historicalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMiddleManagerHost()
|
public String getMiddleManagerHost()
|
||||||
{
|
{
|
||||||
|
|
|
@ -64,6 +64,12 @@ public class DockerConfigProvider implements IntegrationTestingConfigProvider
|
||||||
return "http://" + dockerIp + ":8082";
|
return "http://" + dockerIp + ":8082";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHistoricalUrl()
|
||||||
|
{
|
||||||
|
return "http://" + dockerIp + ":8083";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMiddleManagerHost()
|
public String getMiddleManagerHost()
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,6 +31,8 @@ public interface IntegrationTestingConfig
|
||||||
|
|
||||||
String getBrokerUrl();
|
String getBrokerUrl();
|
||||||
|
|
||||||
|
String getHistoricalUrl();
|
||||||
|
|
||||||
String getMiddleManagerHost();
|
String getMiddleManagerHost();
|
||||||
|
|
||||||
String getZookeeperHosts();
|
String getZookeeperHosts();
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class DruidTestModule implements Module
|
||||||
if (config.getUsername() != null) {
|
if (config.getUsername() != null) {
|
||||||
return new CredentialedHttpClient(new BasicCredentials(config.getUsername(), config.getPassword()), delegate);
|
return new CredentialedHttpClient(new BasicCredentials(config.getUsername(), config.getPassword()), delegate);
|
||||||
} else {
|
} else {
|
||||||
return delegate;
|
return new CredentialedHttpClient(new BasicCredentials("admin", "priest"), delegate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ public class DruidTestRunnerFactory implements ITestRunnerFactory
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
StatusResponseHolder response = client.go(
|
StatusResponseHolder response = client.go(
|
||||||
new Request(HttpMethod.GET, new URL(StringUtils.format("%s/status", host))),
|
new Request(HttpMethod.GET, new URL(StringUtils.format("%s/status/health", host))),
|
||||||
handler
|
handler
|
||||||
).get();
|
).get();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets 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 io.druid.tests.security;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.metamx.http.client.CredentialedHttpClient;
|
||||||
|
import com.metamx.http.client.HttpClient;
|
||||||
|
import com.metamx.http.client.Request;
|
||||||
|
import com.metamx.http.client.auth.BasicCredentials;
|
||||||
|
import com.metamx.http.client.response.StatusResponseHandler;
|
||||||
|
import com.metamx.http.client.response.StatusResponseHolder;
|
||||||
|
import io.druid.guice.annotations.Client;
|
||||||
|
import io.druid.java.util.common.ISE;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
import io.druid.java.util.common.logger.Logger;
|
||||||
|
import io.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import io.druid.server.security.Action;
|
||||||
|
import io.druid.server.security.Resource;
|
||||||
|
import io.druid.server.security.ResourceAction;
|
||||||
|
import io.druid.server.security.ResourceType;
|
||||||
|
import io.druid.testing.IntegrationTestingConfig;
|
||||||
|
import io.druid.testing.guice.DruidTestModuleFactory;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import org.testng.annotations.Guice;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Guice(moduleFactory = DruidTestModuleFactory.class)
|
||||||
|
public class ITBasicAuthConfigurationTest
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(ITBasicAuthConfigurationTest.class);
|
||||||
|
|
||||||
|
private static final TypeReference LOAD_STATUS_TYPE_REFERENCE =
|
||||||
|
new TypeReference<Map<String, Boolean>>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
IntegrationTestingConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ObjectMapper jsonMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Client
|
||||||
|
HttpClient httpClient;
|
||||||
|
|
||||||
|
StatusResponseHandler responseHandler = new StatusResponseHandler(Charsets.UTF_8);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthConfiguration() throws Exception
|
||||||
|
{
|
||||||
|
HttpClient adminClient = new CredentialedHttpClient(
|
||||||
|
new BasicCredentials("admin", "priest"),
|
||||||
|
httpClient
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpClient internalSystemClient = new CredentialedHttpClient(
|
||||||
|
new BasicCredentials("druid_system", "warlock"),
|
||||||
|
httpClient
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpClient newUserClient = new CredentialedHttpClient(
|
||||||
|
new BasicCredentials("druid", "helloworld"),
|
||||||
|
httpClient
|
||||||
|
);
|
||||||
|
|
||||||
|
// check that admin works
|
||||||
|
checkNodeAccess(adminClient);
|
||||||
|
|
||||||
|
// check that internal user works
|
||||||
|
checkNodeAccess(internalSystemClient);
|
||||||
|
|
||||||
|
// create a new user that can read /status
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid/credentials",
|
||||||
|
jsonMapper.writeValueAsBytes(new BasicAuthenticatorCredentialUpdate("helloworld", 5000))
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/roles/druidrole",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid/roles/druidrole",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ResourceAction> permissions = Arrays.asList(
|
||||||
|
new ResourceAction(
|
||||||
|
new Resource(".*", ResourceType.STATE),
|
||||||
|
Action.READ
|
||||||
|
)
|
||||||
|
);
|
||||||
|
byte[] permissionsBytes = jsonMapper.writeValueAsBytes(permissions);
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/roles/druidrole/permissions",
|
||||||
|
permissionsBytes
|
||||||
|
);
|
||||||
|
|
||||||
|
// check that the new user works
|
||||||
|
checkNodeAccess(newUserClient);
|
||||||
|
|
||||||
|
// check loadStatus
|
||||||
|
checkLoadStatus(adminClient);
|
||||||
|
|
||||||
|
|
||||||
|
// create 100 users
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid" + i,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid" + i,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
LOG.info("Finished creating user druid" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the last of 100 users and check that it works
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authentication/db/basic/users/druid99/credentials",
|
||||||
|
jsonMapper.writeValueAsBytes(new BasicAuthenticatorCredentialUpdate("helloworld", 5000))
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
adminClient,
|
||||||
|
HttpMethod.POST,
|
||||||
|
config.getCoordinatorUrl() + "/druid-ext/basic-security/authorization/db/basic/users/druid99/roles/druidrole",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpClient newUser99Client = new CredentialedHttpClient(
|
||||||
|
new BasicCredentials("druid99", "helloworld"),
|
||||||
|
httpClient
|
||||||
|
);
|
||||||
|
|
||||||
|
LOG.info("Checking access for user druid99.");
|
||||||
|
checkNodeAccess(newUser99Client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNodeAccess(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
makeRequest(httpClient, HttpMethod.GET, config.getCoordinatorUrl() + "/status", null);
|
||||||
|
makeRequest(httpClient, HttpMethod.GET, config.getIndexerUrl() + "/status", null);
|
||||||
|
makeRequest(httpClient, HttpMethod.GET, config.getBrokerUrl() + "/status", null);
|
||||||
|
makeRequest(httpClient, HttpMethod.GET, config.getHistoricalUrl() + "/status", null);
|
||||||
|
makeRequest(httpClient, HttpMethod.GET, config.getRouterUrl() + "/status", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLoadStatus(HttpClient httpClient) throws Exception
|
||||||
|
{
|
||||||
|
checkLoadStatusSingle(httpClient, config.getCoordinatorUrl());
|
||||||
|
checkLoadStatusSingle(httpClient, config.getIndexerUrl());
|
||||||
|
checkLoadStatusSingle(httpClient, config.getBrokerUrl());
|
||||||
|
checkLoadStatusSingle(httpClient, config.getHistoricalUrl());
|
||||||
|
checkLoadStatusSingle(httpClient, config.getRouterUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLoadStatusSingle(HttpClient httpClient, String baseUrl) throws Exception
|
||||||
|
{
|
||||||
|
StatusResponseHolder holder = makeRequest(
|
||||||
|
httpClient,
|
||||||
|
HttpMethod.GET,
|
||||||
|
baseUrl + "/druid-ext/basic-security/authentication/loadStatus",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
String content = holder.getContent();
|
||||||
|
Map<String, Boolean> loadStatus = jsonMapper.readValue(content, LOAD_STATUS_TYPE_REFERENCE);
|
||||||
|
|
||||||
|
Assert.assertNotNull(loadStatus.get("basic"));
|
||||||
|
Assert.assertTrue(loadStatus.get("basic"));
|
||||||
|
|
||||||
|
holder = makeRequest(
|
||||||
|
httpClient,
|
||||||
|
HttpMethod.GET,
|
||||||
|
baseUrl + "/druid-ext/basic-security/authorization/loadStatus",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
content = holder.getContent();
|
||||||
|
loadStatus = jsonMapper.readValue(content, LOAD_STATUS_TYPE_REFERENCE);
|
||||||
|
|
||||||
|
Assert.assertNotNull(loadStatus.get("basic"));
|
||||||
|
Assert.assertTrue(loadStatus.get("basic"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatusResponseHolder makeRequest(HttpClient httpClient, HttpMethod method, String url, byte[] content)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Request request = new Request(method, new URL(url));
|
||||||
|
if (content != null) {
|
||||||
|
request.setContent(MediaType.APPLICATION_JSON, content);
|
||||||
|
}
|
||||||
|
int retryCount = 0;
|
||||||
|
|
||||||
|
StatusResponseHolder response;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
response = httpClient.go(
|
||||||
|
request,
|
||||||
|
responseHandler
|
||||||
|
).get();
|
||||||
|
|
||||||
|
if (!response.getStatus().equals(HttpResponseStatus.OK)) {
|
||||||
|
String errMsg = StringUtils.format(
|
||||||
|
"Error while making request to url[%s] status[%s] content[%s]",
|
||||||
|
url,
|
||||||
|
response.getStatus(),
|
||||||
|
response.getContent()
|
||||||
|
);
|
||||||
|
// it can take time for the auth config to propagate, so we retry
|
||||||
|
if (retryCount > 4) {
|
||||||
|
throw new ISE(errMsg);
|
||||||
|
} else {
|
||||||
|
LOG.error(errMsg);
|
||||||
|
LOG.error("retrying in 3000ms, retryCount: " + retryCount);
|
||||||
|
retryCount++;
|
||||||
|
Thread.sleep(3000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
pom.xml
2
pom.xml
|
@ -82,7 +82,6 @@
|
||||||
Need to update Druid to use Jackson 2.6+ -->
|
Need to update Druid to use Jackson 2.6+ -->
|
||||||
<aws.sdk.version>1.10.77</aws.sdk.version>
|
<aws.sdk.version>1.10.77</aws.sdk.version>
|
||||||
<caffeine.version>2.5.5</caffeine.version>
|
<caffeine.version>2.5.5</caffeine.version>
|
||||||
|
|
||||||
<!-- When upgrading ZK, edit docs and integration tests as well (integration-tests/docker-base/setup.sh) -->
|
<!-- When upgrading ZK, edit docs and integration tests as well (integration-tests/docker-base/setup.sh) -->
|
||||||
<zookeeper.version>3.4.10</zookeeper.version>
|
<zookeeper.version>3.4.10</zookeeper.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
@ -121,6 +120,7 @@
|
||||||
<module>extensions-core/lookups-cached-single</module>
|
<module>extensions-core/lookups-cached-single</module>
|
||||||
<module>extensions-core/s3-extensions</module>
|
<module>extensions-core/s3-extensions</module>
|
||||||
<module>extensions-core/simple-client-sslcontext</module>
|
<module>extensions-core/simple-client-sslcontext</module>
|
||||||
|
<module>extensions-core/druid-basic-security</module>
|
||||||
<!-- Community extensions -->
|
<!-- Community extensions -->
|
||||||
<module>extensions-contrib/azure-extensions</module>
|
<module>extensions-contrib/azure-extensions</module>
|
||||||
<module>extensions-contrib/cassandra-storage</module>
|
<module>extensions-contrib/cassandra-storage</module>
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.metamx.http.client.HttpClient;
|
||||||
import com.metamx.http.client.Request;
|
import com.metamx.http.client.Request;
|
||||||
import com.metamx.http.client.response.FullResponseHandler;
|
import com.metamx.http.client.response.FullResponseHandler;
|
||||||
import com.metamx.http.client.response.FullResponseHolder;
|
import com.metamx.http.client.response.FullResponseHolder;
|
||||||
|
import com.metamx.http.client.response.HttpResponseHandler;
|
||||||
import io.druid.client.selector.Server;
|
import io.druid.client.selector.Server;
|
||||||
import io.druid.concurrent.LifecycleLock;
|
import io.druid.concurrent.LifecycleLock;
|
||||||
import io.druid.curator.discovery.ServerDiscoverySelector;
|
import io.druid.curator.discovery.ServerDiscoverySelector;
|
||||||
|
@ -127,10 +128,18 @@ public class DruidLeaderClient
|
||||||
return new Request(httpMethod, new URL(StringUtils.format("%s%s", getCurrentKnownLeader(true), urlPath)));
|
return new Request(httpMethod, new URL(StringUtils.format("%s%s", getCurrentKnownLeader(true), urlPath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FullResponseHolder go(Request request) throws IOException, InterruptedException
|
||||||
|
{
|
||||||
|
return go(request, new FullResponseHandler(Charsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a Request object aimed at the leader. Throws IOException if the leader cannot be located.
|
* Executes a Request object aimed at the leader. Throws IOException if the leader cannot be located.
|
||||||
*/
|
*/
|
||||||
public FullResponseHolder go(Request request) throws IOException, InterruptedException
|
public FullResponseHolder go(
|
||||||
|
Request request,
|
||||||
|
HttpResponseHandler<FullResponseHolder, FullResponseHolder> responseHandler
|
||||||
|
) throws IOException, InterruptedException
|
||||||
{
|
{
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
for (int counter = 0; counter < MAX_RETRIES; counter++) {
|
for (int counter = 0; counter < MAX_RETRIES; counter++) {
|
||||||
|
@ -139,7 +148,7 @@ public class DruidLeaderClient
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
fullResponseHolder = httpClient.go(request, new FullResponseHandler(Charsets.UTF_8)).get();
|
fullResponseHolder = httpClient.go(request, responseHandler).get();
|
||||||
}
|
}
|
||||||
catch (ExecutionException e) {
|
catch (ExecutionException e) {
|
||||||
// Unwrap IOExceptions and ChannelExceptions, re-throw others
|
// Unwrap IOExceptions and ChannelExceptions, re-throw others
|
||||||
|
|
|
@ -44,6 +44,8 @@ import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.SQLRecoverableException;
|
import java.sql.SQLRecoverableException;
|
||||||
import java.sql.SQLTransientException;
|
import java.sql.SQLTransientException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
@ -446,6 +448,78 @@ public abstract class SQLMetadataConnector implements MetadataStorageConnector
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean compareAndSwap(
|
||||||
|
List<MetadataCASUpdate> updates
|
||||||
|
) throws Exception
|
||||||
|
{
|
||||||
|
return getDBI().inTransaction(
|
||||||
|
new TransactionCallback<Boolean>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Boolean inTransaction(Handle handle, TransactionStatus transactionStatus) throws Exception
|
||||||
|
{
|
||||||
|
List<byte[]> currentValues = new ArrayList<byte[]>();
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
for (MetadataCASUpdate update : updates) {
|
||||||
|
byte[] currentValue = handle
|
||||||
|
.createQuery(
|
||||||
|
StringUtils.format(
|
||||||
|
"SELECT %1$s FROM %2$s WHERE %3$s = :key",
|
||||||
|
update.getValueColumn(),
|
||||||
|
update.getTableName(),
|
||||||
|
update.getKeyColumn()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.bind("key", update.getKey())
|
||||||
|
.map(ByteArrayMapper.FIRST)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!Arrays.equals(currentValue, update.getOldValue())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentValues.add(currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap
|
||||||
|
for (int i = 0; i < updates.size(); i++) {
|
||||||
|
MetadataCASUpdate update = updates.get(i);
|
||||||
|
byte[] currentValue = currentValues.get(i);
|
||||||
|
|
||||||
|
if (currentValue == null) {
|
||||||
|
handle.createStatement(
|
||||||
|
StringUtils.format(
|
||||||
|
"INSERT INTO %1$s (%2$s, %3$s) VALUES (:key, :value)",
|
||||||
|
update.getTableName(),
|
||||||
|
update.getKeyColumn(),
|
||||||
|
update.getValueColumn()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.bind("key", update.getKey())
|
||||||
|
.bind("value", update.getNewValue())
|
||||||
|
.execute();
|
||||||
|
} else {
|
||||||
|
handle.createStatement(
|
||||||
|
StringUtils.format(
|
||||||
|
"UPDATE %1$s SET %3$s=:value WHERE %2$s=:key",
|
||||||
|
update.getTableName(),
|
||||||
|
update.getKeyColumn(),
|
||||||
|
update.getValueColumn()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.bind("key", update.getKey())
|
||||||
|
.bind("value", update.getNewValue())
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract DBI getDBI();
|
public abstract DBI getDBI();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -37,6 +37,11 @@ public class AuthenticatorMapper
|
||||||
this.authenticatorMap = authenticatorMap;
|
this.authenticatorMap = authenticatorMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Authenticator> getAuthenticatorMap()
|
||||||
|
{
|
||||||
|
return authenticatorMap;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Authenticator> getAuthenticatorChain()
|
public List<Authenticator> getAuthenticatorChain()
|
||||||
{
|
{
|
||||||
return Lists.newArrayList(authenticatorMap.values());
|
return Lists.newArrayList(authenticatorMap.values());
|
||||||
|
|
|
@ -39,4 +39,9 @@ public class AuthorizerMapper
|
||||||
{
|
{
|
||||||
return authorizerMap.get(name);
|
return authorizerMap.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Authorizer> getAuthorizerMap()
|
||||||
|
{
|
||||||
|
return authorizerMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package io.druid.server.security;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.druid.java.util.common.StringUtils;
|
||||||
|
|
||||||
public class ResourceAction
|
public class ResourceAction
|
||||||
{
|
{
|
||||||
|
@ -75,4 +76,10 @@ public class ResourceAction
|
||||||
result = 31 * result + getAction().hashCode();
|
result = 31 * result + getAction().hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return StringUtils.format("{%s,%s}", resource, action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,6 +348,8 @@ public class CliOverlord extends ServerRunnable
|
||||||
// Can't use /* here because of Guice and Jetty static content conflicts
|
// Can't use /* here because of Guice and Jetty static content conflicts
|
||||||
root.addFilter(GuiceFilter.class, "/druid/*", null);
|
root.addFilter(GuiceFilter.class, "/druid/*", null);
|
||||||
|
|
||||||
|
root.addFilter(GuiceFilter.class, "/druid-ext/*", null);
|
||||||
|
|
||||||
HandlerList handlerList = new HandlerList();
|
HandlerList handlerList = new HandlerList();
|
||||||
handlerList.setHandlers(
|
handlerList.setHandlers(
|
||||||
new Handler[]{
|
new Handler[]{
|
||||||
|
|
|
@ -144,6 +144,8 @@ class CoordinatorJettyServerInitializer implements JettyServerInitializer
|
||||||
if (beOverlord) {
|
if (beOverlord) {
|
||||||
root.addFilter(GuiceFilter.class, "/druid/indexer/*", null);
|
root.addFilter(GuiceFilter.class, "/druid/indexer/*", null);
|
||||||
}
|
}
|
||||||
|
root.addFilter(GuiceFilter.class, "/druid-ext/*", null);
|
||||||
|
|
||||||
// this will be removed in the next major release
|
// this will be removed in the next major release
|
||||||
root.addFilter(GuiceFilter.class, "/coordinator/*", null);
|
root.addFilter(GuiceFilter.class, "/coordinator/*", null);
|
||||||
|
|
||||||
|
|
|
@ -109,10 +109,10 @@ public class RouterJettyServerInitializer implements JettyServerInitializer
|
||||||
jsonMapper
|
jsonMapper
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Can't use '/*' here because of Guice conflicts with AsyncQueryForwardingServlet path
|
// Can't use '/*' here because of Guice conflicts with AsyncQueryForwardingServlet path
|
||||||
root.addFilter(GuiceFilter.class, "/status/*", null);
|
root.addFilter(GuiceFilter.class, "/status/*", null);
|
||||||
root.addFilter(GuiceFilter.class, "/druid/router/*", null);
|
root.addFilter(GuiceFilter.class, "/druid/router/*", null);
|
||||||
|
root.addFilter(GuiceFilter.class, "/druid-ext/*", null);
|
||||||
|
|
||||||
final HandlerList handlerList = new HandlerList();
|
final HandlerList handlerList = new HandlerList();
|
||||||
handlerList.setHandlers(
|
handlerList.setHandlers(
|
||||||
|
|
Loading…
Reference in New Issue