mirror of https://github.com/apache/druid.git
Support LDAP authentication/authorization (#6972)
* Support LDAP authentication/authorization * fixed integration-tests * fixed Travis CI build errors related to druid-security module * fixed failing test * fixed failing test header * added comments, force build * fixes for strict compilation spotbugs checks * removed authenticator rolling credential update feature * removed escalator rolling credential update feature * fixed teamcity inspection deprecated API usage error * fixed checkstyle execution error, removed unused import * removed cached config as part of removing authenticator rolling credential update feature * removed config bundle entity as part of removing authenticator rolling credential update feature * refactored ldao configuration * added support for SSLContext configuration and TLSCertificateChecker * removed check to return authentication failure when user has no group assigned, will be checked and handled by the authorizer * Separate out authorizer checks between metadata-backed store user and LDAP user/groups * refactored BasicSecuritySSLSocketFactory usage to fix strict compilation spotbugs checks * fixes build issue * final review comments updates * final review comments updates * fixed LGTM and spellcheck alerts * Fixed Avatica auth failure error message check * Updated metadata credentials validator exception message string, replaced DB with metadata store
This commit is contained in:
parent
46ddaf3aa1
commit
18758f5228
|
@ -25,8 +25,8 @@ title: "Basic Security"
|
||||||
|
|
||||||
This Apache Druid (incubating) extension adds:
|
This Apache Druid (incubating) extension adds:
|
||||||
|
|
||||||
- an Authenticator which supports [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
- an Authenticator which supports [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) using the Druid metadata store or LDAP as its credentials store
|
||||||
- an Authorizer which implements basic role-based access control
|
- an Authorizer which implements basic role-based access control for Druid metadata store or LDAP users and groups
|
||||||
|
|
||||||
Make sure to [include](../../development/extensions.md#loading-extensions) `druid-basic-security` as an extension.
|
Make sure to [include](../../development/extensions.md#loading-extensions) `druid-basic-security` as an extension.
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ Please see [Authentication and Authorization](../../design/auth.md) for more inf
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The examples in the section will use "MyBasicAuthenticator" and "MyBasicAuthorizer" as names for the Authenticator and Authorizer.
|
The examples in the section will use "MyBasicMetadataAuthenticator", "MyBasicLDAPAuthenticator", "MyBasicMetadataAuthorizer", and "MyBasicLDAPAuthorizer" as names for the Authenticators and Authorizer.
|
||||||
|
|
||||||
These properties are not tied to specific Authenticator or Authorizer instances.
|
These properties are not tied to specific Authenticator or Authorizer instances.
|
||||||
|
|
||||||
|
@ -43,23 +43,27 @@ These configuration properties should be added to the common runtime properties
|
||||||
### Properties
|
### Properties
|
||||||
|Property|Description|Default|required|
|
|Property|Description|Default|required|
|
||||||
|--------|-----------|-------|--------|
|
|--------|-----------|-------|--------|
|
||||||
|`druid.auth.basic.common.pollingPeriod`|Defines in milliseconds how often processes should poll the Coordinator for the current authenticator/authorizer database state.|60000|No|
|
|`druid.auth.basic.common.pollingPeriod`|Defines in milliseconds how often processes should poll the Coordinator for the current Druid metadata store authenticator/authorizer 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.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.maxSyncRetries`|Determines how many times a service will retry if the authentication/authorization Druid metadata store 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|
|
|`druid.auth.basic.common.cacheDirectory`|If defined, snapshots of the basic Authenticator and Authorizer Druid metadata store 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
|
### Creating an Authenticator that uses the Druid metadata store to lookup and validate credentials
|
||||||
```
|
```
|
||||||
druid.auth.authenticatorChain=["MyBasicAuthenticator"]
|
druid.auth.authenticatorChain=["MyBasicMetadataAuthenticator"]
|
||||||
|
|
||||||
druid.auth.authenticator.MyBasicAuthenticator.type=basic
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.type=basic
|
||||||
druid.auth.authenticator.MyBasicAuthenticator.initialAdminPassword=password1
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.initialAdminPassword=password1
|
||||||
druid.auth.authenticator.MyBasicAuthenticator.initialInternalClientPassword=password2
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.initialInternalClientPassword=password2
|
||||||
druid.auth.authenticator.MyBasicAuthenticator.authorizerName=MyBasicAuthorizer
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.credentialsValidator.type=metadata
|
||||||
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.skipOnFailure=false
|
||||||
|
druid.auth.authenticator.MyBasicMetadataAuthenticator.authorizerName=MyBasicMetadataAuthorizer
|
||||||
```
|
```
|
||||||
|
|
||||||
To use the Basic authenticator, add an authenticator with type `basic` to the authenticatorChain.
|
To use the Basic authenticator, add an authenticator with type `basic` to the authenticatorChain.
|
||||||
|
The authenticator needs to also define a credentialsValidator with type 'metadata' or 'ldap'.
|
||||||
|
If credentialsValidator is not specified, type 'metadata' will be used as default.
|
||||||
|
|
||||||
Configuration of the named authenticator is assigned through properties with the form:
|
Configuration of the named authenticator is assigned through properties with the form:
|
||||||
|
|
||||||
|
@ -67,18 +71,41 @@ Configuration of the named authenticator is assigned through properties with the
|
||||||
druid.auth.authenticator.<authenticatorName>.<authenticatorProperty>
|
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.
|
The authenticator configuration examples in the rest of this document will use "MyBasicMetadataAuthenticator" or "MyBasicLDAPAuthenticator" as the name of the authenticators being configured.
|
||||||
|
|
||||||
|
|
||||||
#### Properties
|
#### Properties for Druid metadata store user authentication
|
||||||
|Property|Description|Default|required|
|
|Property|Description|Default|required|
|
||||||
|--------|-----------|-------|--------|
|
|--------|-----------|-------|--------|
|
||||||
|`druid.auth.authenticator.MyBasicAuthenticator.initialAdminPassword`|Initial [Password Provider](../../operations/password-provider.md) 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 not affect its password.|null|No|
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.initialAdminPassword`|Initial [Password Provider](../../operations/password-provider.md) 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 not affect its password.|null|No|
|
||||||
|`druid.auth.authenticator.MyBasicAuthenticator.initialInternalClientPassword`|Initial [Password Provider](../../operations/password-provider.md) for the default internal system user, used for internal process 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 not affect its password.|null|No|
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.initialInternalClientPassword`|Initial [Password Provider](../../operations/password-provider.md) for the default internal system user, used for internal process 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 not affect its password.|null|No|
|
||||||
|`druid.auth.authenticator.MyBasicAuthenticator.enableCacheNotifications`|If true, the Coordinator will notify Druid processes whenever a configuration change to this Authenticator occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.enableCacheNotifications`|If true, the Coordinator will notify Druid processes 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.MyBasicMetadataAuthenticator.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.MyBasicMetadataAuthenticator.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|
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.credentialsValidator.type`|The type of credentials store (metadata) to validate requests credentials.|metadata|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.skipOnFailure`|If true and the request credential doesn't exists or isn't fully configured in the credentials store, the request will proceed to next Authenticator in the chain.|false|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicMetadataAuthenticator.authorizerName`|Authorizer that requests should be directed to|N/A|Yes|
|
||||||
|
|
||||||
|
#### Properties for LDAP user authentication
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.initialAdminPassword`|Initial [Password Provider](../../operations/password-provider.md) 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 not affect its password.|null|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.initialInternalClientPassword`|Initial [Password Provider](../../operations/password-provider.md) for the default internal system user, used for internal process 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 not affect its password.|null|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.enableCacheNotifications`|If true, the Coordinator will notify Druid processes whenever a configuration change to this Authenticator occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.cacheNotificationTimeout`|The timeout in milliseconds for the cache notifications.|5000|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialIterations`|Number of iterations to use for password hashing.|10000|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.type`|The type of credentials store (ldap) to validate requests credentials.|metadata|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.url`|URL of the LDAP server.|null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.bindUser`|LDAP bind user username.|null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.bindPassword`|[Password Provider](../../operations/password-provider.md) LDAP bind user password.|null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.baseDn`|The point from where the LDAP server will search for users.|null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.userSearch`|The filter/expression to use for the search. For example, (&(sAMAccountName=%s)(objectClass=user))|null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.userAttribute`|The attribute id identifying the attribute that will be returned as part of the search. For example, sAMAccountName. |null|Yes|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.credentialVerifyDuration`|The duration in seconds for how long valid credentials are verifiable within the cache when not requested.|600|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.credentialMaxDuration`|The max duration in seconds for valid credentials that can reside in cache regardless of how often they are requested.|3600|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.credentialsValidator.credentialCacheSize`|The valid credentials cache size. The cache uses a LRU policy.|100|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.skipOnFailure`|If true and the request credential doesn't exists or isn't fully configured in the credentials store, the request will proceed to next Authenticator in the chain.|false|No|
|
||||||
|
|`druid.auth.authenticator.MyBasicLDAPAuthenticator.authorizerName`|Authorizer that requests should be directed to.|N/A|Yes|
|
||||||
|
|
||||||
### Creating an Escalator
|
### Creating an Escalator
|
||||||
|
|
||||||
|
@ -87,7 +114,7 @@ The configuration examples in the rest of this document will use "MyBasicAuthent
|
||||||
druid.escalator.type=basic
|
druid.escalator.type=basic
|
||||||
druid.escalator.internalClientUsername=druid_system
|
druid.escalator.internalClientUsername=druid_system
|
||||||
druid.escalator.internalClientPassword=password2
|
druid.escalator.internalClientPassword=password2
|
||||||
druid.escalator.authorizerName=MyBasicAuthorizer
|
druid.escalator.authorizerName=MyBasicMetadataAuthorizer
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Properties
|
#### Properties
|
||||||
|
@ -100,24 +127,40 @@ druid.escalator.authorizerName=MyBasicAuthorizer
|
||||||
|
|
||||||
### Creating an Authorizer
|
### Creating an Authorizer
|
||||||
```
|
```
|
||||||
druid.auth.authorizers=["MyBasicAuthorizer"]
|
druid.auth.authorizers=["MyBasicMetadataAuthorizer"]
|
||||||
|
|
||||||
druid.auth.authorizer.MyBasicAuthorizer.type=basic
|
druid.auth.authorizer.MyBasicMetadataAuthorizer.type=basic
|
||||||
```
|
```
|
||||||
|
|
||||||
To use the Basic authorizer, add an authenticator with type `basic` to the authorizers list.
|
To use the Basic authorizer, add an authorizer with type `basic` to the authorizers list.
|
||||||
|
|
||||||
Configuration of the named authenticator is assigned through properties with the form:
|
Configuration of the named authorizer is assigned through properties with the form:
|
||||||
|
|
||||||
```
|
```
|
||||||
druid.auth.authorizer.<authorizerName>.<authorizerProperty>
|
druid.auth.authorizer.<authorizerName>.<authorizerProperty>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Properties
|
The authorizer configuration examples in the rest of this document will use "MyBasicMetadataAuthorizer" or "MyBasicLDAPAuthorizer" as the name of the authenticators being configured.
|
||||||
|
|
||||||
|
#### Properties for Druid metadata store user authorization
|
||||||
|Property|Description|Default|required|
|
|Property|Description|Default|required|
|
||||||
|--------|-----------|-------|--------|
|
|--------|-----------|-------|--------|
|
||||||
|`druid.auth.authorizer.MyBasicAuthorizer.enableCacheNotifications`|If true, the Coordinator will notify Druid processes whenever a configuration change to this Authorizer occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
|`druid.auth.authorizer.MyBasicMetadataAuthorizer.enableCacheNotifications`|If true, the Coordinator will notify Druid processes 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|
|
|`druid.auth.authorizer.MyBasicMetadataAuthorizer.cacheNotificationTimeout`|The timeout in milliseconds for the cache notifications.|5000|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicMetadataAuthorizer.initialAdminUser`|The initial admin user with role defined in initialAdminRole property if specified, otherwise the default admin role will be assigned.|admin|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicMetadataAuthorizer.initialAdminRole`|The initial admin role to create if it doesn't already exists.|admin|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicMetadataAuthorizer.roleProvider.type`|The type of role provider to authorize requests credentials.|metadata|No
|
||||||
|
|
||||||
|
#### Properties for LDAP user authorization
|
||||||
|
|Property|Description|Default|required|
|
||||||
|
|--------|-----------|-------|--------|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.enableCacheNotifications`|If true, the Coordinator will notify Druid processes whenever a configuration change to this Authorizer occurs, allowing them to immediately update their state without waiting for polling.|true|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.cacheNotificationTimeout`|The timeout in milliseconds for the cache notifications.|5000|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.initialAdminUser`|The initial admin user with role defined in initialAdminRole property if specified, otherwise the default admin role will be assigned.|admin|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.initialAdminRole`|The initial admin role to create if it doesn't already exists.|admin|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.initialAdminGroupMapping`|The initial admin group mapping with role defined in initialAdminRole property if specified, otherwise the default admin role will be assigned. The name of this initial admin group mapping will be set to adminGroupMapping|null|No|
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.roleProvider.type`|The type of role provider (ldap) to authorize requests credentials.|metadata|No
|
||||||
|
|`druid.auth.authorizer.MyBasicLDAPAuthorizer.roleProvider.groupFilters`|Array of LDAP group filters used to filter out the allowed set of groups returned from LDAP search. Filters can be begin with *, or end with ,* to provide configurational flexibility to limit or filter allowed set of groups available to LDAP Authorizer.|null|No|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -157,7 +200,7 @@ Example request body:
|
||||||
|
|
||||||
##### Cache Load Status
|
##### Cache Load Status
|
||||||
`GET(/druid-ext/basic-security/authentication/loadStatus)`
|
`GET(/druid-ext/basic-security/authentication/loadStatus)`
|
||||||
Return the current load status of the local caches of the authentication database.
|
Return the current load status of the local caches of the authentication Druid metadata store.
|
||||||
|
|
||||||
#### Authorization API
|
#### Authorization API
|
||||||
|
|
||||||
|
@ -262,6 +305,30 @@ Create a new user with name {userName}
|
||||||
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})`
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})`
|
||||||
Delete the user with name {userName}
|
Delete the user with name {userName}
|
||||||
|
|
||||||
|
##### Group mapping Creation/Deletion
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings)`
|
||||||
|
Return a list of all group mappings.
|
||||||
|
|
||||||
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings/{groupMappingName})`
|
||||||
|
Return the group mapping and role information of the group mapping with name {groupMappingName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings/{groupMappingName})`
|
||||||
|
Create a new group mapping with name {groupMappingName}
|
||||||
|
Content: JSON group mapping object
|
||||||
|
Example request body:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"groupPattern": "CN=aaa,OU=aaa,OU=Groupings,DC=corp,DC=company,DC=com",
|
||||||
|
"roles": [
|
||||||
|
"user"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings/{groupMappingName})`
|
||||||
|
Delete the group mapping with name {groupMappingName}
|
||||||
|
|
||||||
#### Role Creation/Deletion
|
#### Role Creation/Deletion
|
||||||
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/roles)`
|
`GET(/druid-ext/basic-security/authorization/db/{authorizerName}/roles)`
|
||||||
|
@ -297,7 +364,7 @@ This API supports the following flags:
|
||||||
- `?full`: The output will contain an extra `users` list, containing the users that currently have this role.
|
- `?full`: The output will contain an extra `users` list, containing the users that currently have this role.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"users":["druid"]
|
{"users":["druid"]}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `?simplifyPermissions`: The permissions in the output will contain only a list of `resourceAction` objects, without the extraneous `resourceNamePattern` field. The `users` field will be null when `?full` is not specified.
|
- `?simplifyPermissions`: The permissions in the output will contain only a list of `resourceAction` objects, without the extraneous `resourceNamePattern` field. The `users` field will be null when `?full` is not specified.
|
||||||
|
@ -336,6 +403,12 @@ Assign role {roleName} to user {userName}.
|
||||||
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName}/roles/{roleName})`
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName}/roles/{roleName})`
|
||||||
Unassign role {roleName} from user {userName}
|
Unassign role {roleName} from user {userName}
|
||||||
|
|
||||||
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings/{groupMappingName}/roles/{roleName})`
|
||||||
|
Assign role {roleName} to group mapping {groupMappingName}.
|
||||||
|
|
||||||
|
`DELETE(/druid-ext/basic-security/authorization/db/{authorizerName}/groupmappings/{groupMappingName}/roles/{roleName})`
|
||||||
|
Unassign role {roleName} from group mapping {groupMappingName}
|
||||||
|
|
||||||
|
|
||||||
#### Permissions
|
#### Permissions
|
||||||
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName}/permissions)`
|
`POST(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName}/permissions)`
|
||||||
|
@ -368,7 +441,7 @@ Please see [Defining permissions](#defining-permissions) for more details.
|
||||||
|
|
||||||
##### Cache Load Status
|
##### Cache Load Status
|
||||||
`GET(/druid-ext/basic-security/authorization/loadStatus)`
|
`GET(/druid-ext/basic-security/authorization/loadStatus)`
|
||||||
Return the current load status of the local caches of the authorization database.
|
Return the current load status of the local caches of the authorization Druid metadata store.
|
||||||
|
|
||||||
## Default user accounts
|
## Default user accounts
|
||||||
|
|
||||||
|
@ -462,10 +535,10 @@ Queries on the [system schema tables](../../querying/sql.html#system-schema) req
|
||||||
|
|
||||||
## Configuration Propagation
|
## Configuration Propagation
|
||||||
|
|
||||||
To prevent excessive load on the Coordinator, the Authenticator and Authorizer user/role database state is cached on each Druid process.
|
To prevent excessive load on the Coordinator, the Authenticator and Authorizer user/role Druid metadata store state is cached on each Druid process.
|
||||||
|
|
||||||
Each process 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.
|
Each process will periodically poll the Coordinator for the latest Druid metadata store 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 process with the updated database state. This behavior is controlled by the `enableCacheNotifications` and `cacheNotificationTimeout` properties on Authenticators and Authorizers.
|
When a configuration update occurs, the Coordinator can optionally notify each process with the updated Druid metadata store 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 process.
|
Note that because of the caching, changes made to the user/role Druid metadata store may not be immediately reflected at each Druid process.
|
||||||
|
|
|
@ -27,23 +27,32 @@ public class BasicAuthDBConfig
|
||||||
|
|
||||||
private final PasswordProvider initialAdminPassword;
|
private final PasswordProvider initialAdminPassword;
|
||||||
private final PasswordProvider initialInternalClientPassword;
|
private final PasswordProvider initialInternalClientPassword;
|
||||||
|
private final String initialAdminUser;
|
||||||
|
private final String initialAdminRole;
|
||||||
|
private final String initialAdminGroupMapping;
|
||||||
private final boolean enableCacheNotifications;
|
private final boolean enableCacheNotifications;
|
||||||
private final long cacheNotificationTimeout;
|
private final long cacheNotificationTimeout;
|
||||||
private final int iterations;
|
private final int credentialIterations;
|
||||||
|
|
||||||
public BasicAuthDBConfig(
|
public BasicAuthDBConfig(
|
||||||
final PasswordProvider initialAdminPassword,
|
final PasswordProvider initialAdminPassword,
|
||||||
final PasswordProvider initialInternalClientPassword,
|
final PasswordProvider initialInternalClientPassword,
|
||||||
final Boolean enableCacheNotifications,
|
final String initialAdminUser,
|
||||||
final Long cacheNotificationTimeout,
|
final String initialAdminRole,
|
||||||
final int iterations
|
final String initialAdminGroupMapping,
|
||||||
|
final boolean enableCacheNotifications,
|
||||||
|
final long cacheNotificationTimeout,
|
||||||
|
final int credentialIterations
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.initialAdminPassword = initialAdminPassword;
|
this.initialAdminPassword = initialAdminPassword;
|
||||||
this.initialInternalClientPassword = initialInternalClientPassword;
|
this.initialInternalClientPassword = initialInternalClientPassword;
|
||||||
|
this.initialAdminUser = initialAdminUser;
|
||||||
|
this.initialAdminRole = initialAdminRole;
|
||||||
|
this.initialAdminGroupMapping = initialAdminGroupMapping;
|
||||||
this.enableCacheNotifications = enableCacheNotifications;
|
this.enableCacheNotifications = enableCacheNotifications;
|
||||||
this.cacheNotificationTimeout = cacheNotificationTimeout;
|
this.cacheNotificationTimeout = cacheNotificationTimeout;
|
||||||
this.iterations = iterations;
|
this.credentialIterations = credentialIterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PasswordProvider getInitialAdminPassword()
|
public PasswordProvider getInitialAdminPassword()
|
||||||
|
@ -56,6 +65,21 @@ public class BasicAuthDBConfig
|
||||||
return initialInternalClientPassword;
|
return initialInternalClientPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getInitialAdminUser()
|
||||||
|
{
|
||||||
|
return initialAdminUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitialAdminRole()
|
||||||
|
{
|
||||||
|
return initialAdminRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitialAdminGroupMapping()
|
||||||
|
{
|
||||||
|
return initialAdminGroupMapping;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnableCacheNotifications()
|
public boolean isEnableCacheNotifications()
|
||||||
{
|
{
|
||||||
return enableCacheNotifications;
|
return enableCacheNotifications;
|
||||||
|
@ -66,8 +90,8 @@ public class BasicAuthDBConfig
|
||||||
return cacheNotificationTimeout;
|
return cacheNotificationTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIterations()
|
public int getCredentialIterations()
|
||||||
{
|
{
|
||||||
return iterations;
|
return credentialIterations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic;
|
||||||
|
|
||||||
|
import org.apache.druid.metadata.PasswordProvider;
|
||||||
|
|
||||||
|
public class BasicAuthLDAPConfig
|
||||||
|
{
|
||||||
|
private final String url;
|
||||||
|
private final String bindUser;
|
||||||
|
private final PasswordProvider bindPassword;
|
||||||
|
private final String baseDn;
|
||||||
|
private final String userSearch;
|
||||||
|
private final String userAttribute;
|
||||||
|
private final int credentialIterations;
|
||||||
|
private final Integer credentialVerifyDuration;
|
||||||
|
private final Integer credentialMaxDuration;
|
||||||
|
private final Integer credentialCacheSize;
|
||||||
|
|
||||||
|
public BasicAuthLDAPConfig(
|
||||||
|
final String url,
|
||||||
|
final String bindUser,
|
||||||
|
final PasswordProvider bindPassword,
|
||||||
|
final String baseDn,
|
||||||
|
final String userSearch,
|
||||||
|
final String userAttribute,
|
||||||
|
final int credentialIterations,
|
||||||
|
final Integer credentialVerifyDuration,
|
||||||
|
final Integer credentialMaxDuration,
|
||||||
|
final Integer credentialCacheSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
this.bindUser = bindUser;
|
||||||
|
this.bindPassword = bindPassword;
|
||||||
|
this.baseDn = baseDn;
|
||||||
|
this.userSearch = userSearch;
|
||||||
|
this.userAttribute = userAttribute;
|
||||||
|
this.credentialIterations = credentialIterations;
|
||||||
|
this.credentialVerifyDuration = credentialVerifyDuration;
|
||||||
|
this.credentialMaxDuration = credentialMaxDuration;
|
||||||
|
this.credentialCacheSize = credentialCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl()
|
||||||
|
{
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindUser()
|
||||||
|
{
|
||||||
|
return bindUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordProvider getBindPassword()
|
||||||
|
{
|
||||||
|
return bindPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseDn()
|
||||||
|
{
|
||||||
|
return baseDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserSearch()
|
||||||
|
{
|
||||||
|
return userSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAttribute()
|
||||||
|
{
|
||||||
|
return userAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCredentialIterations()
|
||||||
|
{
|
||||||
|
return credentialIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCredentialVerifyDuration()
|
||||||
|
{
|
||||||
|
return credentialVerifyDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCredentialMaxDuration()
|
||||||
|
{
|
||||||
|
return credentialMaxDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCredentialCacheSize()
|
||||||
|
{
|
||||||
|
return credentialCacheSize;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.apache.druid.metadata.PasswordProvider;
|
||||||
|
|
||||||
|
public class BasicAuthSSLConfig
|
||||||
|
{
|
||||||
|
@JsonProperty
|
||||||
|
private String protocol;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String trustStoreType;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String trustStorePath;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String trustStoreAlgorithm;
|
||||||
|
|
||||||
|
@JsonProperty("trustStorePassword")
|
||||||
|
private PasswordProvider trustStorePasswordProvider;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String keyStorePath;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String keyStoreType;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String certAlias;
|
||||||
|
|
||||||
|
@JsonProperty("keyStorePassword")
|
||||||
|
private PasswordProvider keyStorePasswordProvider;
|
||||||
|
|
||||||
|
@JsonProperty("keyManagerPassword")
|
||||||
|
private PasswordProvider keyManagerPasswordProvider;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private String keyManagerFactoryAlgorithm;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private Boolean validateHostnames;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthSSLConfig(
|
||||||
|
@JsonProperty("protocol") String protocol,
|
||||||
|
@JsonProperty("trustStoreType") String trustStoreType,
|
||||||
|
@JsonProperty("trustStorePath") String trustStorePath,
|
||||||
|
@JsonProperty("trustStoreAlgorithm") String trustStoreAlgorithm,
|
||||||
|
@JsonProperty("trustStorePassword") PasswordProvider trustStorePasswordProvider,
|
||||||
|
@JsonProperty("keyStorePath") String keyStorePath,
|
||||||
|
@JsonProperty("keyStoreType") String keyStoreType,
|
||||||
|
@JsonProperty("certAlias") String certAlias,
|
||||||
|
@JsonProperty("keyStorePassword") PasswordProvider keyStorePasswordProvider,
|
||||||
|
@JsonProperty("keyManagerPassword") PasswordProvider keyManagerPasswordProvider,
|
||||||
|
@JsonProperty("keyManagerFactoryAlgorithm") String keyManagerFactoryAlgorithm,
|
||||||
|
@JsonProperty("validateHostnames") Boolean validateHostnames
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.protocol = protocol;
|
||||||
|
this.trustStoreType = trustStoreType;
|
||||||
|
this.trustStorePath = trustStorePath;
|
||||||
|
this.trustStoreAlgorithm = trustStoreAlgorithm;
|
||||||
|
this.trustStorePasswordProvider = trustStorePasswordProvider;
|
||||||
|
this.keyStorePath = keyStorePath;
|
||||||
|
this.keyStoreType = keyStoreType;
|
||||||
|
this.certAlias = certAlias;
|
||||||
|
this.keyStorePasswordProvider = keyStorePasswordProvider;
|
||||||
|
this.keyManagerPasswordProvider = keyManagerPasswordProvider;
|
||||||
|
this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
|
||||||
|
this.validateHostnames = validateHostnames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getProtocol()
|
||||||
|
{
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getTrustStoreType()
|
||||||
|
{
|
||||||
|
return trustStoreType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getTrustStorePath()
|
||||||
|
{
|
||||||
|
return trustStorePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getTrustStoreAlgorithm()
|
||||||
|
{
|
||||||
|
return trustStoreAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("trustStorePassword")
|
||||||
|
public PasswordProvider getTrustStorePasswordProvider()
|
||||||
|
{
|
||||||
|
return trustStorePasswordProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getKeyStorePath()
|
||||||
|
{
|
||||||
|
return keyStorePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getKeyStoreType()
|
||||||
|
{
|
||||||
|
return keyStoreType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getCertAlias()
|
||||||
|
{
|
||||||
|
return certAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("keyStorePassword")
|
||||||
|
public PasswordProvider getKeyStorePasswordProvider()
|
||||||
|
{
|
||||||
|
return keyStorePasswordProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("keyManagerPassword")
|
||||||
|
public PasswordProvider getKeyManagerPasswordProvider()
|
||||||
|
{
|
||||||
|
return keyManagerPasswordProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getKeyManagerFactoryAlgorithm()
|
||||||
|
{
|
||||||
|
return keyManagerFactoryAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Boolean getValidateHostnames()
|
||||||
|
{
|
||||||
|
return validateHostnames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SSLClientConfig{" +
|
||||||
|
"protocol='" + protocol + '\'' +
|
||||||
|
", trustStoreType='" + trustStoreType + '\'' +
|
||||||
|
", trustStorePath='" + trustStorePath + '\'' +
|
||||||
|
", trustStoreAlgorithm='" + trustStoreAlgorithm + '\'' +
|
||||||
|
", keyStorePath='" + keyStorePath + '\'' +
|
||||||
|
", keyStoreType='" + keyStoreType + '\'' +
|
||||||
|
", certAlias='" + certAlias + '\'' +
|
||||||
|
", keyManagerFactoryAlgorithm='" + keyManagerFactoryAlgorithm + '\'' +
|
||||||
|
", validateHostnames='" + validateHostnames + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,8 +26,10 @@ import org.apache.druid.java.util.common.RE;
|
||||||
import org.apache.druid.java.util.common.StringUtils;
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
import org.apache.druid.java.util.common.logger.Logger;
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
|
||||||
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -48,7 +50,9 @@ public class BasicAuthUtils
|
||||||
private static final Logger log = new Logger(BasicAuthUtils.class);
|
private static final Logger log = new Logger(BasicAuthUtils.class);
|
||||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||||
public static final String ADMIN_NAME = "admin";
|
public static final String ADMIN_NAME = "admin";
|
||||||
|
public static final String ADMIN_GROUP_MAPPING_NAME = "adminGroupMapping";
|
||||||
public static final String INTERNAL_USER_NAME = "druid_system";
|
public static final String INTERNAL_USER_NAME = "druid_system";
|
||||||
|
public static final String SEARCH_RESULT_CONTEXT_KEY = "searchResult";
|
||||||
|
|
||||||
// PBKDF2WithHmacSHA512 is chosen since it has built-in support in Java8.
|
// 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
|
// Argon2 (https://github.com/p-h-c/phc-winner-argon2) is newer but the only presently
|
||||||
|
@ -57,6 +61,9 @@ public class BasicAuthUtils
|
||||||
// 256-bit salt should be more than sufficient for uniqueness, expected user count is on the order of thousands.
|
// 256-bit salt should be more than sufficient for uniqueness, expected user count is on the order of thousands.
|
||||||
public static final int SALT_LENGTH = 32;
|
public static final int SALT_LENGTH = 32;
|
||||||
public static final int DEFAULT_KEY_ITERATIONS = 10000;
|
public static final int DEFAULT_KEY_ITERATIONS = 10000;
|
||||||
|
public static final int DEFAULT_CREDENTIAL_VERIFY_DURATION_SECONDS = 600;
|
||||||
|
public static final int DEFAULT_CREDENTIAL_MAX_DURATION_SECONDS = 3600;
|
||||||
|
public static final int DEFAULT_CREDENTIAL_CACHE_SIZE = 100;
|
||||||
public static final int KEY_LENGTH = 512;
|
public static final int KEY_LENGTH = 512;
|
||||||
public static final String ALGORITHM = "PBKDF2WithHmacSHA512";
|
public static final String ALGORITHM = "PBKDF2WithHmacSHA512";
|
||||||
|
|
||||||
|
@ -70,6 +77,11 @@ public class BasicAuthUtils
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final TypeReference AUTHORIZER_GROUP_MAPPING_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<Map<String, BasicAuthorizerGroupMapping>>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
public static final TypeReference AUTHORIZER_ROLE_MAP_TYPE_REFERENCE =
|
public static final TypeReference AUTHORIZER_ROLE_MAP_TYPE_REFERENCE =
|
||||||
new TypeReference<Map<String, BasicAuthorizerRole>>()
|
new TypeReference<Map<String, BasicAuthorizerRole>>()
|
||||||
{
|
{
|
||||||
|
@ -80,6 +92,11 @@ public class BasicAuthUtils
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final TypeReference AUTHORIZER_GROUP_MAPPING_AND_ROLE_MAP_TYPE_REFERENCE =
|
||||||
|
new TypeReference<GroupMappingAndRoleMap>()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
public static byte[] hashPassword(final char[] password, final byte[] salt, final int iterations)
|
public static byte[] hashPassword(final char[] password, final byte[] salt, final int iterations)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -155,7 +172,7 @@ public class BasicAuthUtils
|
||||||
userMap = objectMapper.readValue(userMapBytes, AUTHENTICATOR_USER_MAP_TYPE_REFERENCE);
|
userMap = objectMapper.readValue(userMapBytes, AUTHENTICATOR_USER_MAP_TYPE_REFERENCE);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException("Couldn't deserialize authenticator userMap!", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return userMap;
|
return userMap;
|
||||||
|
@ -170,7 +187,7 @@ public class BasicAuthUtils
|
||||||
return objectMapper.writeValueAsBytes(userMap);
|
return objectMapper.writeValueAsBytes(userMap);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new ISE(ioe, "WTF? Couldn't serialize userMap!");
|
throw new ISE(ioe, "Couldn't serialize authenticator userMap!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +204,7 @@ public class BasicAuthUtils
|
||||||
userMap = objectMapper.readValue(userMapBytes, BasicAuthUtils.AUTHORIZER_USER_MAP_TYPE_REFERENCE);
|
userMap = objectMapper.readValue(userMapBytes, BasicAuthUtils.AUTHORIZER_USER_MAP_TYPE_REFERENCE);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException("Couldn't deserialize authorizer userMap!", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return userMap;
|
return userMap;
|
||||||
|
@ -199,7 +216,36 @@ public class BasicAuthUtils
|
||||||
return objectMapper.writeValueAsBytes(userMap);
|
return objectMapper.writeValueAsBytes(userMap);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new ISE(ioe, "WTF? Couldn't serialize userMap!");
|
throw new ISE(ioe, "Couldn't serialize authorizer userMap!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, BasicAuthorizerGroupMapping> deserializeAuthorizerGroupMappingMap(
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
byte[] groupMappingMapBytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap;
|
||||||
|
if (groupMappingMapBytes == null) {
|
||||||
|
groupMappingMap = new HashMap<>();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
groupMappingMap = objectMapper.readValue(groupMappingMapBytes, BasicAuthUtils.AUTHORIZER_GROUP_MAPPING_MAP_TYPE_REFERENCE);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException("Couldn't deserialize authorizer groupMappingMap!", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupMappingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] serializeAuthorizerGroupMappingMap(ObjectMapper objectMapper, Map<String, BasicAuthorizerGroupMapping> groupMappingMap)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsBytes(groupMappingMap);
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new ISE(ioe, "Couldn't serialize authorizer groupMappingMap!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +262,7 @@ public class BasicAuthUtils
|
||||||
roleMap = objectMapper.readValue(roleMapBytes, BasicAuthUtils.AUTHORIZER_ROLE_MAP_TYPE_REFERENCE);
|
roleMap = objectMapper.readValue(roleMapBytes, BasicAuthUtils.AUTHORIZER_ROLE_MAP_TYPE_REFERENCE);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException("Couldn't deserialize authorizer roleMap!", ioe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return roleMap;
|
return roleMap;
|
||||||
|
@ -228,7 +274,7 @@ public class BasicAuthUtils
|
||||||
return objectMapper.writeValueAsBytes(roleMap);
|
return objectMapper.writeValueAsBytes(roleMap);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new ISE(ioe, "WTF? Couldn't serialize roleMap!");
|
throw new ISE(ioe, "Couldn't serialize authorizer roleMap!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic;
|
||||||
|
|
||||||
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw this when a request is unauthorized and we want to send a 401 response back.
|
||||||
|
*/
|
||||||
|
public class BasicSecurityAuthenticationException extends IllegalArgumentException
|
||||||
|
{
|
||||||
|
public BasicSecurityAuthenticationException(String formatText, Object... arguments)
|
||||||
|
{
|
||||||
|
super(StringUtils.nonStrictFormat(formatText, arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicSecurityAuthenticationException(Throwable t, String formatText, Object... arguments)
|
||||||
|
{
|
||||||
|
super(StringUtils.nonStrictFormat(formatText, arguments), t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import org.apache.druid.guice.JsonConfigProvider;
|
||||||
import org.apache.druid.guice.LazySingleton;
|
import org.apache.druid.guice.LazySingleton;
|
||||||
import org.apache.druid.guice.LifecycleModule;
|
import org.apache.druid.guice.LifecycleModule;
|
||||||
import org.apache.druid.initialization.DruidModule;
|
import org.apache.druid.initialization.DruidModule;
|
||||||
|
import org.apache.druid.java.util.http.client.NettyHttpClient;
|
||||||
import org.apache.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
import org.apache.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
import org.apache.druid.security.basic.authentication.BasicHTTPEscalator;
|
import org.apache.druid.security.basic.authentication.BasicHTTPEscalator;
|
||||||
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
@ -72,14 +73,19 @@ public class BasicSecurityDruidModule implements DruidModule
|
||||||
{
|
{
|
||||||
JsonConfigProvider.bind(binder, "druid.auth.basic.common", BasicAuthCommonCacheConfig.class);
|
JsonConfigProvider.bind(binder, "druid.auth.basic.common", BasicAuthCommonCacheConfig.class);
|
||||||
JsonConfigProvider.bind(binder, "druid.auth.basic.composition", BasicAuthClassCompositionConfig.class);
|
JsonConfigProvider.bind(binder, "druid.auth.basic.composition", BasicAuthClassCompositionConfig.class);
|
||||||
|
JsonConfigProvider.bind(binder, "druid.auth.basic.ssl", BasicAuthSSLConfig.class);
|
||||||
|
|
||||||
LifecycleModule.register(binder, BasicAuthenticatorMetadataStorageUpdater.class);
|
LifecycleModule.register(binder, BasicAuthenticatorMetadataStorageUpdater.class);
|
||||||
LifecycleModule.register(binder, BasicAuthorizerMetadataStorageUpdater.class);
|
LifecycleModule.register(binder, BasicAuthorizerMetadataStorageUpdater.class);
|
||||||
LifecycleModule.register(binder, BasicAuthenticatorCacheManager.class);
|
LifecycleModule.register(binder, BasicAuthenticatorCacheManager.class);
|
||||||
LifecycleModule.register(binder, BasicAuthorizerCacheManager.class);
|
LifecycleModule.register(binder, BasicAuthorizerCacheManager.class);
|
||||||
|
LifecycleModule.register(binder, BasicAuthenticatorCacheNotifier.class);
|
||||||
|
LifecycleModule.register(binder, BasicAuthorizerCacheNotifier.class);
|
||||||
|
|
||||||
Jerseys.addResource(binder, BasicAuthenticatorResource.class);
|
Jerseys.addResource(binder, BasicAuthenticatorResource.class);
|
||||||
Jerseys.addResource(binder, BasicAuthorizerResource.class);
|
Jerseys.addResource(binder, BasicAuthorizerResource.class);
|
||||||
|
|
||||||
|
binder.requestStaticInjection(BasicSecuritySSLSocketFactory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -201,7 +207,7 @@ public class BasicSecurityDruidModule implements DruidModule
|
||||||
NoopBasicAuthorizerCacheNotifier.class
|
NoopBasicAuthorizerCacheNotifier.class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<? extends Module> getJacksonModules()
|
public List<? extends Module> getJacksonModules()
|
||||||
{
|
{
|
||||||
|
@ -209,7 +215,8 @@ public class BasicSecurityDruidModule implements DruidModule
|
||||||
new SimpleModule("BasicDruidSecurity").registerSubtypes(
|
new SimpleModule("BasicDruidSecurity").registerSubtypes(
|
||||||
BasicHTTPAuthenticator.class,
|
BasicHTTPAuthenticator.class,
|
||||||
BasicHTTPEscalator.class,
|
BasicHTTPEscalator.class,
|
||||||
BasicRoleBasedAuthorizer.class
|
BasicRoleBasedAuthorizer.class,
|
||||||
|
NettyHttpClient.class
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.server.security.TLSCertificateChecker;
|
||||||
|
import org.apache.druid.server.security.TLSUtils;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class BasicSecuritySSLSocketFactory extends SSLSocketFactory
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(BasicSecuritySSLSocketFactory.class);
|
||||||
|
private SSLSocketFactory sf;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private static BasicAuthSSLConfig basicAuthSSLConfig;
|
||||||
|
@Inject
|
||||||
|
private static TLSCertificateChecker certificateChecker;
|
||||||
|
|
||||||
|
public BasicSecuritySSLSocketFactory()
|
||||||
|
{
|
||||||
|
SSLContext ctx = new TLSUtils.ClientSSLContextBuilder()
|
||||||
|
.setProtocol(basicAuthSSLConfig.getProtocol())
|
||||||
|
.setTrustStoreType(basicAuthSSLConfig.getTrustStoreType())
|
||||||
|
.setTrustStorePath(basicAuthSSLConfig.getTrustStorePath())
|
||||||
|
.setTrustStoreAlgorithm(basicAuthSSLConfig.getTrustStoreAlgorithm())
|
||||||
|
.setTrustStorePasswordProvider(basicAuthSSLConfig.getTrustStorePasswordProvider())
|
||||||
|
.setKeyStoreType(basicAuthSSLConfig.getKeyStoreType())
|
||||||
|
.setKeyStorePath(basicAuthSSLConfig.getKeyStorePath())
|
||||||
|
.setKeyStoreAlgorithm(basicAuthSSLConfig.getKeyManagerFactoryAlgorithm())
|
||||||
|
.setCertAlias(basicAuthSSLConfig.getCertAlias())
|
||||||
|
.setKeyStorePasswordProvider(basicAuthSSLConfig.getKeyStorePasswordProvider())
|
||||||
|
.setKeyManagerFactoryPasswordProvider(basicAuthSSLConfig.getKeyManagerPasswordProvider())
|
||||||
|
.setValidateHostnames(basicAuthSSLConfig.getValidateHostnames())
|
||||||
|
.setCertificateChecker(certificateChecker)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
sf = ctx.getSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SocketFactory getDefault()
|
||||||
|
{
|
||||||
|
return new BasicSecuritySSLSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getDefaultCipherSuites()
|
||||||
|
{
|
||||||
|
return sf.getDefaultCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites()
|
||||||
|
{
|
||||||
|
return sf.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(
|
||||||
|
Socket s,
|
||||||
|
String host,
|
||||||
|
int port,
|
||||||
|
boolean autoClose) throws IOException
|
||||||
|
{
|
||||||
|
return sf.createSocket(s, host, port, autoClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(String host, int port) throws IOException
|
||||||
|
{
|
||||||
|
return sf.createSocket(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(
|
||||||
|
String host,
|
||||||
|
int port,
|
||||||
|
InetAddress localHost,
|
||||||
|
int localPort) throws IOException
|
||||||
|
{
|
||||||
|
return sf.createSocket(host, port, localHost, localPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(InetAddress host, int port) throws IOException
|
||||||
|
{
|
||||||
|
return sf.createSocket(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(
|
||||||
|
InetAddress address,
|
||||||
|
int port,
|
||||||
|
InetAddress localAddress,
|
||||||
|
int localPort) throws IOException
|
||||||
|
{
|
||||||
|
return sf.createSocket(address, port, localAddress, localPort);
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,6 +107,7 @@ public class CommonCacheNotifier
|
||||||
Pair<String, byte[]> update = updateQueue.take();
|
Pair<String, byte[]> update = updateQueue.take();
|
||||||
String authorizer = update.lhs;
|
String authorizer = update.lhs;
|
||||||
byte[] serializedMap = update.rhs;
|
byte[] serializedMap = update.rhs;
|
||||||
|
|
||||||
BasicAuthDBConfig authorizerConfig = itemConfigMap.get(update.lhs);
|
BasicAuthDBConfig authorizerConfig = itemConfigMap.get(update.lhs);
|
||||||
if (!authorizerConfig.isEnableCacheNotifications()) {
|
if (!authorizerConfig.isEnableCacheNotifications()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -157,20 +158,23 @@ public class CommonCacheNotifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ListenableFuture<StatusResponseHolder>> sendUpdate(String updatedAuthorizerPrefix, byte[] serializedUserMap)
|
private List<ListenableFuture<StatusResponseHolder>> sendUpdate(String updatedAuthenticatorPrefix, byte[] serializedEntity)
|
||||||
{
|
{
|
||||||
List<ListenableFuture<StatusResponseHolder>> futures = new ArrayList<>();
|
List<ListenableFuture<StatusResponseHolder>> futures = new ArrayList<>();
|
||||||
for (NodeType nodeType : NODE_TYPES) {
|
for (NodeType nodeType : NODE_TYPES) {
|
||||||
DruidNodeDiscovery nodeDiscovery = discoveryProvider.getForNodeType(nodeType);
|
DruidNodeDiscovery nodeDiscovery = discoveryProvider.getForNodeType(nodeType);
|
||||||
Collection<DiscoveryDruidNode> nodes = nodeDiscovery.getAllNodes();
|
Collection<DiscoveryDruidNode> nodes = nodeDiscovery.getAllNodes();
|
||||||
for (DiscoveryDruidNode node : nodes) {
|
for (DiscoveryDruidNode node : nodes) {
|
||||||
URL listenerURL = getListenerURL(node.getDruidNode(), baseUrl, updatedAuthorizerPrefix);
|
URL listenerURL = getListenerURL(
|
||||||
|
node.getDruidNode(),
|
||||||
|
StringUtils.format(baseUrl, StringUtils.urlEncode(updatedAuthenticatorPrefix))
|
||||||
|
);
|
||||||
|
|
||||||
// best effort, if this fails, remote node will poll and pick up the update eventually
|
// best effort, if this fails, remote node will poll and pick up the update eventually
|
||||||
Request req = new Request(HttpMethod.POST, listenerURL);
|
Request req = new Request(HttpMethod.POST, listenerURL);
|
||||||
req.setContent(MediaType.APPLICATION_JSON, serializedUserMap);
|
req.setContent(MediaType.APPLICATION_JSON, serializedEntity);
|
||||||
|
|
||||||
BasicAuthDBConfig itemConfig = itemConfigMap.get(updatedAuthorizerPrefix);
|
BasicAuthDBConfig itemConfig = itemConfigMap.get(updatedAuthenticatorPrefix);
|
||||||
|
|
||||||
ListenableFuture<StatusResponseHolder> future = httpClient.go(
|
ListenableFuture<StatusResponseHolder> future = httpClient.go(
|
||||||
req,
|
req,
|
||||||
|
@ -183,18 +187,19 @@ public class CommonCacheNotifier
|
||||||
return futures;
|
return futures;
|
||||||
}
|
}
|
||||||
|
|
||||||
private URL getListenerURL(DruidNode druidNode, String baseUrl, String itemName)
|
private URL getListenerURL(DruidNode druidNode, String baseUrl)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return new URL(
|
return new URL(
|
||||||
druidNode.getServiceScheme(),
|
druidNode.getServiceScheme(),
|
||||||
druidNode.getHost(),
|
druidNode.getHost(),
|
||||||
druidNode.getPortToUse(),
|
druidNode.getPortToUse(),
|
||||||
StringUtils.format(baseUrl, StringUtils.urlEncode(itemName))
|
baseUrl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch (MalformedURLException mue) {
|
catch (MalformedURLException mue) {
|
||||||
LOG.error(callerName + ":WTF? Malformed url for DruidNode[%s] and itemName[%s]", druidNode, itemName);
|
LOG.error(callerName + ":WTF? Malformed url for DruidNode[%s] and baseUrl[%s]", druidNode, baseUrl);
|
||||||
|
|
||||||
throw new RuntimeException(mue);
|
throw new RuntimeException(mue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,14 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import org.apache.druid.java.util.common.IAE;
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
import org.apache.druid.metadata.PasswordProvider;
|
import org.apache.druid.metadata.PasswordProvider;
|
||||||
import org.apache.druid.security.basic.BasicAuthDBConfig;
|
import org.apache.druid.security.basic.BasicAuthDBConfig;
|
||||||
import org.apache.druid.security.basic.BasicAuthUtils;
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.apache.druid.security.basic.BasicSecurityAuthenticationException;
|
||||||
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
import org.apache.druid.security.basic.authentication.validator.CredentialsValidator;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
import org.apache.druid.security.basic.authentication.validator.MetadataStoreCredentialsValidator;
|
||||||
import org.apache.druid.server.security.AuthConfig;
|
import org.apache.druid.server.security.AuthConfig;
|
||||||
import org.apache.druid.server.security.AuthenticationResult;
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
import org.apache.druid.server.security.Authenticator;
|
import org.apache.druid.server.security.Authenticator;
|
||||||
|
@ -46,17 +47,20 @@ import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@JsonTypeName("basic")
|
@JsonTypeName("basic")
|
||||||
public class BasicHTTPAuthenticator implements Authenticator
|
public class BasicHTTPAuthenticator implements Authenticator
|
||||||
{
|
{
|
||||||
private final Provider<BasicAuthenticatorCacheManager> cacheManager;
|
private static final Logger LOG = new Logger(BasicHTTPAuthenticator.class);
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String authorizerName;
|
private final String authorizerName;
|
||||||
private final BasicAuthDBConfig dbConfig;
|
private final BasicAuthDBConfig dbConfig;
|
||||||
|
private final CredentialsValidator credentialsValidator;
|
||||||
|
private final boolean skipOnFailure;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public BasicHTTPAuthenticator(
|
public BasicHTTPAuthenticator(
|
||||||
|
@ -67,7 +71,9 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
@JsonProperty("initialInternalClientPassword") PasswordProvider initialInternalClientPassword,
|
@JsonProperty("initialInternalClientPassword") PasswordProvider initialInternalClientPassword,
|
||||||
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
||||||
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout,
|
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout,
|
||||||
@JsonProperty("credentialIterations") Integer credentialIterations
|
@JsonProperty("credentialIterations") Integer credentialIterations,
|
||||||
|
@JsonProperty("skipOnFailure") Boolean skipOnFailure,
|
||||||
|
@JsonProperty("credentialsValidator") CredentialsValidator credentialsValidator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -75,11 +81,19 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
this.dbConfig = new BasicAuthDBConfig(
|
this.dbConfig = new BasicAuthDBConfig(
|
||||||
initialAdminPassword,
|
initialAdminPassword,
|
||||||
initialInternalClientPassword,
|
initialInternalClientPassword,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
enableCacheNotifications == null ? true : enableCacheNotifications,
|
enableCacheNotifications == null ? true : enableCacheNotifications,
|
||||||
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
||||||
credentialIterations == null ? BasicAuthUtils.DEFAULT_KEY_ITERATIONS : credentialIterations
|
credentialIterations == null ? BasicAuthUtils.DEFAULT_KEY_ITERATIONS : credentialIterations
|
||||||
);
|
);
|
||||||
this.cacheManager = cacheManager;
|
if (credentialsValidator == null) {
|
||||||
|
this.credentialsValidator = new MetadataStoreCredentialsValidator(cacheManager);
|
||||||
|
} else {
|
||||||
|
this.credentialsValidator = credentialsValidator;
|
||||||
|
}
|
||||||
|
this.skipOnFailure = skipOnFailure == null ? false : skipOnFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,11 +119,7 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkCredentials(user, password.toCharArray())) {
|
return credentialsValidator.validateCredentials(name, authorizerName, user, password.toCharArray());
|
||||||
return new AuthenticationResult(user, authorizerName, name, null);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,8 +175,7 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, encodedUserSecret is not null, indicating that the request intends to perform
|
// At this point, encodedUserSecret is not null, indicating that the request intends to perform
|
||||||
// Basic HTTP authentication. If any errors occur with the authentication, we send a 401 response immediately
|
// Basic HTTP authentication.
|
||||||
// and do not proceed further down the filter chain.
|
|
||||||
String decodedUserSecret = BasicAuthUtils.decodeUserSecret(encodedUserSecret);
|
String decodedUserSecret = BasicAuthUtils.decodeUserSecret(encodedUserSecret);
|
||||||
if (decodedUserSecret == null) {
|
if (decodedUserSecret == null) {
|
||||||
// We recognized a Basic auth header, but could not decode the user secret.
|
// We recognized a Basic auth header, but could not decode the user secret.
|
||||||
|
@ -176,6 +185,7 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
|
|
||||||
String[] splits = decodedUserSecret.split(":");
|
String[] splits = decodedUserSecret.split(":");
|
||||||
if (splits.length != 2) {
|
if (splits.length != 2) {
|
||||||
|
// The decoded user secret is not of the right format
|
||||||
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -183,12 +193,34 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
String user = splits[0];
|
String user = splits[0];
|
||||||
char[] password = splits[1].toCharArray();
|
char[] password = splits[1].toCharArray();
|
||||||
|
|
||||||
if (checkCredentials(user, password)) {
|
// If any authentication error occurs we send a 401 response immediately and do not proceed further down the filter chain.
|
||||||
AuthenticationResult authenticationResult = new AuthenticationResult(user, authorizerName, name, null);
|
// If the authentication result is null and skipOnFailure property is false, we send a 401 response and do not proceed
|
||||||
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
|
// further down the filter chain. If the authentication result is null and skipOnFailure is true then move on to the next filter.
|
||||||
filterChain.doFilter(servletRequest, servletResponse);
|
// Authentication results, for instance, can be null if a user doesn't exists within a user store
|
||||||
} else {
|
try {
|
||||||
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
AuthenticationResult authenticationResult = credentialsValidator.validateCredentials(
|
||||||
|
name,
|
||||||
|
authorizerName,
|
||||||
|
user,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
if (authenticationResult != null) {
|
||||||
|
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
} else {
|
||||||
|
if (skipOnFailure) {
|
||||||
|
LOG.info("Skipping failed authenticator %s ", name);
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
} else {
|
||||||
|
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (BasicSecurityAuthenticationException ex) {
|
||||||
|
LOG.info("Exception authenticating user %s - %s", user, ex.getMessage());
|
||||||
|
httpResp.sendError(HttpServletResponse.SC_UNAUTHORIZED,
|
||||||
|
String.format(Locale.getDefault(),
|
||||||
|
"User authentication failed username[%s].", user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,29 +230,4 @@ public class BasicHTTPAuthenticator implements Authenticator
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCredentials(String username, char[] password)
|
|
||||||
{
|
|
||||||
Map<String, BasicAuthenticatorUser> userMap = cacheManager.get().getUserMap(name);
|
|
||||||
if (userMap == null) {
|
|
||||||
throw new IAE("No userMap is available for authenticator: [%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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package org.apache.druid.security.basic.authentication;
|
||||||
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 com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
import org.apache.druid.java.util.http.client.CredentialedHttpClient;
|
import org.apache.druid.java.util.http.client.CredentialedHttpClient;
|
||||||
import org.apache.druid.java.util.http.client.HttpClient;
|
import org.apache.druid.java.util.http.client.HttpClient;
|
||||||
import org.apache.druid.java.util.http.client.auth.BasicCredentials;
|
import org.apache.druid.java.util.http.client.auth.BasicCredentials;
|
||||||
|
@ -32,6 +33,8 @@ import org.apache.druid.server.security.Escalator;
|
||||||
@JsonTypeName("basic")
|
@JsonTypeName("basic")
|
||||||
public class BasicHTTPEscalator implements Escalator
|
public class BasicHTTPEscalator implements Escalator
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = new Logger(BasicHTTPEscalator.class);
|
||||||
|
|
||||||
private final String internalClientUsername;
|
private final String internalClientUsername;
|
||||||
private final PasswordProvider internalClientPassword;
|
private final PasswordProvider internalClientPassword;
|
||||||
private final String authorizerName;
|
private final String authorizerName;
|
||||||
|
@ -51,6 +54,7 @@ public class BasicHTTPEscalator implements Escalator
|
||||||
@Override
|
@Override
|
||||||
public HttpClient createEscalatedClient(HttpClient baseClient)
|
public HttpClient createEscalatedClient(HttpClient baseClient)
|
||||||
{
|
{
|
||||||
|
LOG.debug("----------- Creating escalated client");
|
||||||
return new CredentialedHttpClient(
|
return new CredentialedHttpClient(
|
||||||
new BasicCredentials(internalClientUsername, internalClientPassword.getPassword()),
|
new BasicCredentials(internalClientUsername, internalClientPassword.getPassword()),
|
||||||
baseClient
|
baseClient
|
||||||
|
@ -60,6 +64,7 @@ public class BasicHTTPEscalator implements Escalator
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationResult createEscalatedAuthenticationResult()
|
public AuthenticationResult createEscalatedAuthenticationResult()
|
||||||
{
|
{
|
||||||
|
LOG.debug("----------- Creating escalated authentication result. username: %s", this.internalClientUsername);
|
||||||
// if you found your self asking why the authenticatedBy field is set to null please read this:
|
// if you found your self asking why the authenticatedBy field is set to null please read this:
|
||||||
// https://github.com/apache/incubator-druid/pull/5706#discussion_r185940889
|
// https://github.com/apache/incubator-druid/pull/5706#discussion_r185940889
|
||||||
return new AuthenticationResult(internalClientUsername, authorizerName, null, null);
|
return new AuthenticationResult(internalClientUsername, authorizerName, null, null);
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authentication;
|
||||||
|
|
||||||
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class LdapUserPrincipal implements Principal
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(LdapUserPrincipal.class);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final BasicAuthenticatorCredentials credentials;
|
||||||
|
private final SearchResult searchResult;
|
||||||
|
private final Instant createdAt;
|
||||||
|
private final AtomicReference<Instant> lastVerified = new AtomicReference<>();
|
||||||
|
|
||||||
|
public LdapUserPrincipal(
|
||||||
|
String name,
|
||||||
|
BasicAuthenticatorCredentials credentials,
|
||||||
|
SearchResult searchResult
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this(name, credentials, searchResult, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
private LdapUserPrincipal(
|
||||||
|
String name,
|
||||||
|
BasicAuthenticatorCredentials credentials,
|
||||||
|
SearchResult searchResult,
|
||||||
|
Instant createdAt
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Objects.requireNonNull(name, "name is required");
|
||||||
|
Objects.requireNonNull(credentials, "credentials is required");
|
||||||
|
Objects.requireNonNull(searchResult, "searchResult is required");
|
||||||
|
Objects.requireNonNull(createdAt, "createdAt is required");
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.credentials = credentials;
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.lastVerified.set(createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResult getSearchResult()
|
||||||
|
{
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedAt()
|
||||||
|
{
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLastVerified()
|
||||||
|
{
|
||||||
|
return lastVerified.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSameCredentials(char[] password)
|
||||||
|
{
|
||||||
|
byte[] recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
password,
|
||||||
|
this.credentials.getSalt(),
|
||||||
|
this.credentials.getIterations()
|
||||||
|
);
|
||||||
|
if (Arrays.equals(recalculatedHash, credentials.getHash())) {
|
||||||
|
this.lastVerified.set(Instant.now());
|
||||||
|
LOG.debug("Refereshing lastVerified principal user '%s'", this.name);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired(int duration, int maxDuration)
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long maxCutoff = now - (maxDuration * 1000L);
|
||||||
|
if (this.createdAt.toEpochMilli() < maxCutoff) {
|
||||||
|
long cutoff = now - (duration * 1000L);
|
||||||
|
if (this.lastVerified.get().toEpochMilli() < cutoff) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return StringUtils.format(
|
||||||
|
"LdapUserPrincipal[name=%s, searchResult=%s, createdAt=%s, lastVerified=%s]",
|
||||||
|
name,
|
||||||
|
searchResult,
|
||||||
|
createdAt,
|
||||||
|
lastVerified);
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,12 +30,11 @@ import java.util.Map;
|
||||||
public interface BasicAuthenticatorCacheManager
|
public interface BasicAuthenticatorCacheManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Update this cache manager's local state with fresh information pushed by the coordinator.
|
* Update this cache manager's local state of user map with fresh information pushed by the coordinator.
|
||||||
*
|
|
||||||
* @param authenticatorPrefix The name of the authenticator this update applies to.
|
* @param authenticatorPrefix The name of the authenticator this update applies to.
|
||||||
* @param serializedUserMap The updated, serialized user map
|
* @param serializedUserMap The updated, serialized user map
|
||||||
*/
|
*/
|
||||||
void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap);
|
void handleAuthenticatorUserMapUpdate(String authenticatorPrefix, byte[] serializedUserMap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the cache manager's local view of the user map for the authenticator named `authenticatorPrefix`.
|
* Return the cache manager's local view of the user map for the authenticator named `authenticatorPrefix`.
|
||||||
|
|
|
@ -30,5 +30,5 @@ public interface BasicAuthenticatorCacheNotifier
|
||||||
* @param updatedAuthenticatorPrefix Name of authenticator being updated
|
* @param updatedAuthenticatorPrefix Name of authenticator being updated
|
||||||
* @param updatedUserMap User map state
|
* @param updatedUserMap User map state
|
||||||
*/
|
*/
|
||||||
void addUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap);
|
void addUserUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
@ManageLifecycle
|
@ManageLifecycle
|
||||||
public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
||||||
{
|
{
|
||||||
|
|
||||||
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
private CommonCacheNotifier cacheNotifier;
|
private final CommonCacheNotifier userCacheNotifier;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoordinatorBasicAuthenticatorCacheNotifier(
|
public CoordinatorBasicAuthenticatorCacheNotifier(
|
||||||
|
@ -53,7 +52,7 @@ public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenti
|
||||||
@EscalatedClient HttpClient httpClient
|
@EscalatedClient HttpClient httpClient
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
cacheNotifier = new CommonCacheNotifier(
|
userCacheNotifier = new CommonCacheNotifier(
|
||||||
initAuthenticatorConfigMap(authenticatorMapper),
|
initAuthenticatorConfigMap(authenticatorMapper),
|
||||||
discoveryProvider,
|
discoveryProvider,
|
||||||
httpClient,
|
httpClient,
|
||||||
|
@ -66,11 +65,11 @@ public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenti
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
if (!lifecycleLock.canStart()) {
|
if (!lifecycleLock.canStart()) {
|
||||||
throw new ISE("can't start.");
|
throw new ISE("Can't start.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cacheNotifier.start();
|
userCacheNotifier.start();
|
||||||
lifecycleLock.started();
|
lifecycleLock.started();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -85,7 +84,7 @@ public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenti
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
cacheNotifier.stop();
|
userCacheNotifier.stop();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
lifecycleLock.exitStop();
|
lifecycleLock.exitStop();
|
||||||
|
@ -93,10 +92,10 @@ public class CoordinatorBasicAuthenticatorCacheNotifier implements BasicAuthenti
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String updatedAuthorizerPrefix, byte[] updatedUserMap)
|
public void addUserUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
||||||
{
|
{
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
cacheNotifier.addUpdate(updatedAuthorizerPrefix, updatedUserMap);
|
userCacheNotifier.addUpdate(updatedAuthenticatorPrefix, updatedUserMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, BasicAuthDBConfig> initAuthenticatorConfigMap(AuthenticatorMapper mapper)
|
private Map<String, BasicAuthDBConfig> initAuthenticatorConfigMap(AuthenticatorMapper mapper)
|
||||||
|
|
|
@ -101,7 +101,7 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
throw new ISE("can't start.");
|
throw new ISE("can't start.");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("Starting DefaultBasicAuthenticatorCacheManager.");
|
LOG.info("Starting CoordinatorPollingBasicAuthenticatorCacheManager.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initUserMaps();
|
initUserMaps();
|
||||||
|
@ -113,17 +113,17 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
() -> {
|
() -> {
|
||||||
try {
|
try {
|
||||||
long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
|
long randomDelay = ThreadLocalRandom.current().nextLong(0, commonCacheConfig.getMaxRandomDelay());
|
||||||
LOG.debug("Inserting random polling delay of [%s] ms", randomDelay);
|
LOG.debug("Inserting cachedUserMaps random polling delay of [%s] ms", randomDelay);
|
||||||
Thread.sleep(randomDelay);
|
Thread.sleep(randomDelay);
|
||||||
|
|
||||||
LOG.debug("Scheduled cache poll is running");
|
LOG.debug("Scheduled user cache poll is running");
|
||||||
for (String authenticatorPrefix : authenticatorPrefixes) {
|
for (String authenticatorPrefix : authenticatorPrefixes) {
|
||||||
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorPrefix, false);
|
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorPrefix, false);
|
||||||
if (userMap != null) {
|
if (userMap != null) {
|
||||||
cachedUserMaps.put(authenticatorPrefix, userMap);
|
cachedUserMaps.put(authenticatorPrefix, userMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Scheduled cache poll is done");
|
LOG.debug("Scheduled user cache poll is done");
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
@ -132,7 +132,7 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
);
|
);
|
||||||
|
|
||||||
lifecycleLock.started();
|
lifecycleLock.started();
|
||||||
LOG.info("Started DefaultBasicAuthenticatorCacheManager.");
|
LOG.info("Started CoordinatorPollingBasicAuthenticatorCacheManager.");
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
lifecycleLock.exitStart();
|
lifecycleLock.exitStart();
|
||||||
|
@ -146,15 +146,15 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
throw new ISE("can't stop.");
|
throw new ISE("can't stop.");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("DefaultBasicAuthenticatorCacheManager is stopping.");
|
LOG.info("CoordinatorPollingBasicAuthenticatorCacheManager is stopping.");
|
||||||
exec.shutdown();
|
exec.shutdown();
|
||||||
LOG.info("DefaultBasicAuthenticatorCacheManager is stopped.");
|
LOG.info("CoordinatorPollingBasicAuthenticatorCacheManager is stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
public void handleAuthenticatorUserMapUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
{
|
{
|
||||||
LOG.debug("Received cache update for authenticator [%s].", authenticatorPrefix);
|
LOG.debug("Received user cache update for authenticator [%s].", authenticatorPrefix);
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
try {
|
try {
|
||||||
cachedUserMaps.put(
|
cachedUserMaps.put(
|
||||||
|
@ -170,7 +170,7 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
LOG.makeAlert(e, "WTF? Could not deserialize user map received from coordinator.").emit();
|
LOG.makeAlert(e, "Could not deserialize user map received from coordinator.").emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +279,7 @@ public class CoordinatorPollingBasicAuthenticatorCacheManager implements BasicAu
|
||||||
if (authenticator instanceof BasicHTTPAuthenticator) {
|
if (authenticator instanceof BasicHTTPAuthenticator) {
|
||||||
String authenticatorName = entry.getKey();
|
String authenticatorName = entry.getKey();
|
||||||
authenticatorPrefixes.add(authenticatorName);
|
authenticatorPrefixes.add(authenticatorName);
|
||||||
|
|
||||||
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorName, true);
|
Map<String, BasicAuthenticatorUser> userMap = fetchUserMapFromCoordinator(authenticatorName, true);
|
||||||
if (userMap != null) {
|
if (userMap != null) {
|
||||||
cachedUserMaps.put(authenticatorName, userMap);
|
cachedUserMaps.put(authenticatorName, userMap);
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class MetadataStoragePollingBasicAuthenticatorCacheManager implements Bas
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
public void handleAuthenticatorUserMapUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,14 @@ package org.apache.druid.security.basic.authentication.db.cache;
|
||||||
*/
|
*/
|
||||||
public class NoopBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
public class NoopBasicAuthenticatorCacheNotifier implements 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
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
public void addUserUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
||||||
{
|
{
|
||||||
// Do nothing as this is a noop implementation
|
// Do nothing as this is a noop implementation
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
return ScheduledExecutors.Signal.STOP;
|
return ScheduledExecutors.Signal.STOP;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
LOG.debug("Scheduled db poll is running");
|
LOG.debug("Scheduled db userMap poll is running");
|
||||||
for (String authenticatorPrefix : authenticatorPrefixes) {
|
for (String authenticatorPrefix : authenticatorPrefixes) {
|
||||||
|
|
||||||
byte[] userMapBytes = getCurrentUserMapBytes(authenticatorPrefix);
|
byte[] userMapBytes = getCurrentUserMapBytes(authenticatorPrefix);
|
||||||
|
@ -185,7 +185,7 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
cachedUserMaps.put(authenticatorPrefix, new BasicAuthenticatorUserMapBundle(userMap, userMapBytes));
|
cachedUserMaps.put(authenticatorPrefix, new BasicAuthenticatorUserMapBundle(userMap, userMapBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Scheduled db poll is done");
|
LOG.debug("Scheduled db userMap poll is done");
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
@ -277,7 +277,7 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
{
|
{
|
||||||
cachedUserMaps.forEach(
|
cachedUserMaps.forEach(
|
||||||
(authenticatorName, userMapBundle) -> {
|
(authenticatorName, userMapBundle) -> {
|
||||||
cacheNotifier.addUpdate(authenticatorName, userMapBundle.getSerializedUserMap());
|
cacheNotifier.addUserUpdate(authenticatorName, userMapBundle.getSerializedUserMap());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -287,40 +287,6 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
return StringUtils.format("basic_authentication_%s_%s", keyPrefix, 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)
|
private void createUserInternal(String prefix, String userName)
|
||||||
{
|
{
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
|
@ -330,12 +296,7 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
} else {
|
} else {
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
try {
|
updateRetryDelay();
|
||||||
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);
|
throw new ISE("Could not create user[%s] due to concurrent update contention.", userName);
|
||||||
}
|
}
|
||||||
|
@ -349,16 +310,21 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
} else {
|
} else {
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
try {
|
updateRetryDelay();
|
||||||
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);
|
throw new ISE("Could not delete user[%s] due to concurrent update contention.", userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateRetryDelay()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
throw new RuntimeException(ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setUserCredentialsInternal(String prefix, String userName, BasicAuthenticatorCredentialUpdate update)
|
private void setUserCredentialsInternal(String prefix, String userName, BasicAuthenticatorCredentialUpdate update)
|
||||||
{
|
{
|
||||||
BasicAuthenticatorCredentials credentials;
|
BasicAuthenticatorCredentials credentials;
|
||||||
|
@ -371,7 +337,7 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
credentials = new BasicAuthenticatorCredentials(
|
credentials = new BasicAuthenticatorCredentials(
|
||||||
new BasicAuthenticatorCredentialUpdate(
|
new BasicAuthenticatorCredentialUpdate(
|
||||||
update.getPassword(),
|
update.getPassword(),
|
||||||
authenticator.getDbConfig().getIterations()
|
authenticator.getDbConfig().getCredentialIterations()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -442,4 +408,38 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdater implements Basi
|
||||||
byte[] newValue = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, userMap);
|
byte[] newValue = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, userMap);
|
||||||
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.addUserUpdate(prefix, newValue);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ public class BasicAuthenticatorResource
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for update notifications for the auth storage
|
* Listen for users update notifications for the auth storage
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("/listen/{authenticatorName}")
|
@Path("/listen/{authenticatorName}")
|
||||||
|
@ -224,6 +224,6 @@ public class BasicAuthenticatorResource
|
||||||
byte[] serializedUserMap
|
byte[] serializedUserMap
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return handler.authenticatorUpdateListener(authenticatorName, serializedUserMap);
|
return handler.authenticatorUserUpdateListener(authenticatorName, serializedUserMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public interface BasicAuthenticatorResourceHandler
|
||||||
Response refreshAll();
|
Response refreshAll();
|
||||||
|
|
||||||
// non-coordinator methods
|
// non-coordinator methods
|
||||||
Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap);
|
Response authenticatorUserUpdateListener(String authenticatorName, byte[] serializedUserMap);
|
||||||
|
|
||||||
// common methods
|
// common methods
|
||||||
Response getLoadStatus();
|
Response getLoadStatus();
|
||||||
|
|
|
@ -179,7 +179,7 @@ public class CoordinatorBasicAuthenticatorResourceHandler implements BasicAuthen
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
public Response authenticatorUserUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
||||||
{
|
{
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
@ -189,9 +189,8 @@ public class CoordinatorBasicAuthenticatorResourceHandler implements BasicAuthen
|
||||||
{
|
{
|
||||||
Map<String, Boolean> loadStatus = new HashMap<>();
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
authenticatorMap.forEach(
|
authenticatorMap.forEach(
|
||||||
(authenticatorName, authenticator) -> {
|
(authenticatorName, authenticator) ->
|
||||||
loadStatus.put(authenticatorName, storageUpdater.getCachedUserMap(authenticatorName) != null);
|
loadStatus.put(authenticatorName, storageUpdater.getCachedUserMap(authenticatorName) != null)
|
||||||
}
|
|
||||||
);
|
);
|
||||||
return Response.ok(loadStatus).build();
|
return Response.ok(loadStatus).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,11 +109,11 @@ public class DefaultBasicAuthenticatorResourceHandler implements BasicAuthentica
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response authenticatorUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
public Response authenticatorUserUpdateListener(String authenticatorName, byte[] serializedUserMap)
|
||||||
{
|
{
|
||||||
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
final BasicHTTPAuthenticator authenticator = authenticatorMap.get(authenticatorName);
|
||||||
if (authenticator == null) {
|
if (authenticator == null) {
|
||||||
String errMsg = StringUtils.format("Received update for unknown authenticator[%s]", authenticatorName);
|
String errMsg = StringUtils.format("Received user update for unknown authenticator[%s]", authenticatorName);
|
||||||
log.error(errMsg);
|
log.error(errMsg);
|
||||||
return Response.status(Response.Status.BAD_REQUEST)
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
.entity(ImmutableMap.<String, Object>of(
|
.entity(ImmutableMap.<String, Object>of(
|
||||||
|
@ -123,7 +123,7 @@ public class DefaultBasicAuthenticatorResourceHandler implements BasicAuthentica
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheManager.handleAuthenticatorUpdate(authenticatorName, serializedUserMap);
|
cacheManager.handleAuthenticatorUserMapUpdate(authenticatorName, serializedUserMap);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,10 +131,8 @@ public class DefaultBasicAuthenticatorResourceHandler implements BasicAuthentica
|
||||||
public Response getLoadStatus()
|
public Response getLoadStatus()
|
||||||
{
|
{
|
||||||
Map<String, Boolean> loadStatus = new HashMap<>();
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
authenticatorMap.forEach(
|
authenticatorMap.forEach((authenticatorName, authenticator) ->
|
||||||
(authenticatorName, authenticator) -> {
|
loadStatus.put(authenticatorName, cacheManager.getUserMap(authenticatorName) != null)
|
||||||
loadStatus.put(authenticatorName, cacheManager.getUserMap(authenticatorName) != null);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
return Response.ok(loadStatus).build();
|
return Response.ok(loadStatus).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class BasicAuthenticatorCredentials
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (o == null || getClass() != o.getClass()) {
|
if (o == null || !getClass().equals(o.getClass())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,11 +86,8 @@ public class BasicAuthenticatorCredentials
|
||||||
if (getIterations() != that.getIterations()) {
|
if (getIterations() != that.getIterations()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Arrays.equals(getSalt(), that.getSalt())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Arrays.equals(getHash(), that.getHash());
|
|
||||||
|
|
||||||
|
return Arrays.equals(getSalt(), that.getSalt()) && Arrays.equals(getHash(), that.getHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -55,16 +55,14 @@ public class BasicAuthenticatorUser
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (o == null || getClass() != o.getClass()) {
|
if (o == null || !getClass().equals(o.getClass())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicAuthenticatorUser that = (BasicAuthenticatorUser) o;
|
BasicAuthenticatorUser that = (BasicAuthenticatorUser) o;
|
||||||
|
|
||||||
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
return (getName() != null ? getName().equals(that.getName()) : that.getName() == null)
|
||||||
return false;
|
&& (getCredentials() != null ? getCredentials().equals(that.getCredentials()) : that.getCredentials() == null);
|
||||||
}
|
|
||||||
return getCredentials() != null ? getCredentials().equals(that.getCredentials()) : that.getCredentials() == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authentication.validator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = MetadataStoreCredentialsValidator.class)
|
||||||
|
@JsonSubTypes(value = {
|
||||||
|
@JsonSubTypes.Type(name = "metadata", value = MetadataStoreCredentialsValidator.class),
|
||||||
|
@JsonSubTypes.Type(name = "ldap", value = LDAPCredentialsValidator.class),
|
||||||
|
})
|
||||||
|
public interface CredentialsValidator
|
||||||
|
{
|
||||||
|
AuthenticationResult validateCredentials(
|
||||||
|
String authenticatorName,
|
||||||
|
String authorizerName,
|
||||||
|
String username,
|
||||||
|
char[] password
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,296 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authentication.validator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.metadata.PasswordProvider;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthLDAPConfig;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.apache.druid.security.basic.BasicSecurityAuthenticationException;
|
||||||
|
import org.apache.druid.security.basic.BasicSecuritySSLSocketFactory;
|
||||||
|
import org.apache.druid.security.basic.authentication.LdapUserPrincipal;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.naming.AuthenticationException;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import javax.naming.ldap.LdapName;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@JsonTypeName("ldap")
|
||||||
|
public class LDAPCredentialsValidator implements CredentialsValidator
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(LDAPCredentialsValidator.class);
|
||||||
|
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||||
|
|
||||||
|
private final LruBlockCache cache;
|
||||||
|
|
||||||
|
private final BasicAuthLDAPConfig ldapConfig;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public LDAPCredentialsValidator(
|
||||||
|
@JsonProperty("url") String url,
|
||||||
|
@JsonProperty("bindUser") String bindUser,
|
||||||
|
@JsonProperty("bindPassword") PasswordProvider bindPassword,
|
||||||
|
@JsonProperty("baseDn") String baseDn,
|
||||||
|
@JsonProperty("userSearch") String userSearch,
|
||||||
|
@JsonProperty("userAttribute") String userAttribute,
|
||||||
|
@JsonProperty("credentialIterations") Integer credentialIterations,
|
||||||
|
@JsonProperty("credentialVerifyDuration") Integer credentialVerifyDuration,
|
||||||
|
@JsonProperty("credentialMaxDuration") Integer credentialMaxDuration,
|
||||||
|
@JsonProperty("credentialCacheSize") Integer credentialCacheSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.ldapConfig = new BasicAuthLDAPConfig(
|
||||||
|
url,
|
||||||
|
bindUser,
|
||||||
|
bindPassword,
|
||||||
|
baseDn,
|
||||||
|
userSearch,
|
||||||
|
userAttribute,
|
||||||
|
credentialIterations == null ? BasicAuthUtils.DEFAULT_KEY_ITERATIONS : credentialIterations,
|
||||||
|
credentialVerifyDuration == null ? BasicAuthUtils.DEFAULT_CREDENTIAL_VERIFY_DURATION_SECONDS : credentialVerifyDuration,
|
||||||
|
credentialMaxDuration == null ? BasicAuthUtils.DEFAULT_CREDENTIAL_MAX_DURATION_SECONDS : credentialMaxDuration,
|
||||||
|
credentialCacheSize == null ? BasicAuthUtils.DEFAULT_CREDENTIAL_CACHE_SIZE : credentialCacheSize
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cache = new LruBlockCache(
|
||||||
|
this.ldapConfig.getCredentialCacheSize(),
|
||||||
|
this.ldapConfig.getCredentialVerifyDuration(),
|
||||||
|
this.ldapConfig.getCredentialMaxDuration()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties bindProperties(BasicAuthLDAPConfig ldapConfig)
|
||||||
|
{
|
||||||
|
Properties properties = commonProperties(ldapConfig);
|
||||||
|
properties.put(Context.SECURITY_PRINCIPAL, ldapConfig.getBindUser());
|
||||||
|
properties.put(Context.SECURITY_CREDENTIALS, ldapConfig.getBindPassword().getPassword());
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties userProperties(BasicAuthLDAPConfig ldapConfig, LdapName userDn, char[] password)
|
||||||
|
{
|
||||||
|
Properties properties = commonProperties(ldapConfig);
|
||||||
|
properties.put(Context.SECURITY_PRINCIPAL, userDn.toString());
|
||||||
|
properties.put(Context.SECURITY_CREDENTIALS, String.valueOf(password));
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties commonProperties(BasicAuthLDAPConfig ldapConfig)
|
||||||
|
{
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
|
properties.put(Context.PROVIDER_URL, ldapConfig.getUrl());
|
||||||
|
properties.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
|
if (StringUtils.toLowerCase(ldapConfig.getUrl()).startsWith("ldaps://")) {
|
||||||
|
properties.put(Context.SECURITY_PROTOCOL, "ssl");
|
||||||
|
properties.put("java.naming.ldap.factory.socket", BasicSecuritySSLSocketFactory.class.getName());
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationResult validateCredentials(
|
||||||
|
String authenticatorName,
|
||||||
|
String authorizerName,
|
||||||
|
String username,
|
||||||
|
char[] password
|
||||||
|
)
|
||||||
|
{
|
||||||
|
SearchResult userResult;
|
||||||
|
LdapName userDn;
|
||||||
|
Map<String, Object> contextMap = new HashMap<>();
|
||||||
|
|
||||||
|
LdapUserPrincipal principal = this.cache.getOrExpire(username);
|
||||||
|
if (principal != null && principal.hasSameCredentials(password)) {
|
||||||
|
contextMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, principal.getSearchResult());
|
||||||
|
return new AuthenticationResult(username, authorizerName, authenticatorName, contextMap);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
InitialDirContext dirContext = new InitialDirContext(bindProperties(this.ldapConfig));
|
||||||
|
try {
|
||||||
|
userResult = getLdapUserObject(this.ldapConfig, dirContext, username);
|
||||||
|
if (userResult == null) {
|
||||||
|
LOG.debug("User not found: %s", username);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
userDn = new LdapName(userResult.getNameInNamespace());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
dirContext.close();
|
||||||
|
}
|
||||||
|
catch (Exception ignored) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NamingException e) {
|
||||||
|
LOG.error(e, "Exception during user lookup");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePassword(this.ldapConfig, userDn, password)) {
|
||||||
|
LOG.debug("Password incorrect for LDAP user %s", username);
|
||||||
|
throw new BasicSecurityAuthenticationException("User LDAP authentication failed username[%s].", userDn.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] salt = BasicAuthUtils.generateSalt();
|
||||||
|
byte[] hash = BasicAuthUtils.hashPassword(password, salt, this.ldapConfig.getCredentialIterations());
|
||||||
|
LdapUserPrincipal newPrincipal = new LdapUserPrincipal(
|
||||||
|
username,
|
||||||
|
new BasicAuthenticatorCredentials(salt, hash, this.ldapConfig.getCredentialIterations()),
|
||||||
|
userResult
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cache.put(username, newPrincipal);
|
||||||
|
contextMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, userResult);
|
||||||
|
return new AuthenticationResult(username, authorizerName, authenticatorName, contextMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
SearchResult getLdapUserObject(BasicAuthLDAPConfig ldapConfig, DirContext context, String username)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
SearchControls sc = new SearchControls();
|
||||||
|
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||||
|
sc.setReturningAttributes(new String[] {ldapConfig.getUserAttribute(), "memberOf" });
|
||||||
|
NamingEnumeration<SearchResult> results = context.search(
|
||||||
|
ldapConfig.getBaseDn(),
|
||||||
|
StringUtils.format(ldapConfig.getUserSearch(), username),
|
||||||
|
sc);
|
||||||
|
try {
|
||||||
|
if (!results.hasMore()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return results.next();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
results.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NamingException e) {
|
||||||
|
LOG.debug(e, "Unable to find user '%s'", username);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean validatePassword(BasicAuthLDAPConfig ldapConfig, LdapName userDn, char[] password)
|
||||||
|
{
|
||||||
|
InitialDirContext context = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
context = new InitialDirContext(userProperties(ldapConfig, userDn, password));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (AuthenticationException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (NamingException e) {
|
||||||
|
LOG.error(e, "Exception during LDAP authentication username[%s]", userDn.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
if (context != null) {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ignored) {
|
||||||
|
LOG.warn("Exception closing LDAP context");
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LruBlockCache extends LinkedHashMap<String, LdapUserPrincipal>
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 7509410739092012261L;
|
||||||
|
|
||||||
|
private final int cacheSize;
|
||||||
|
private final int duration;
|
||||||
|
private final int maxDuration;
|
||||||
|
|
||||||
|
public LruBlockCache(int cacheSize, int duration, int maxDuration)
|
||||||
|
{
|
||||||
|
super(16, 0.75f, true);
|
||||||
|
this.cacheSize = cacheSize;
|
||||||
|
this.duration = duration;
|
||||||
|
this.maxDuration = maxDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<String, LdapUserPrincipal> eldest)
|
||||||
|
{
|
||||||
|
return size() > cacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
LdapUserPrincipal getOrExpire(String identity)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
LOCK.lock();
|
||||||
|
LdapUserPrincipal principal = get(identity);
|
||||||
|
if (principal != null) {
|
||||||
|
if (principal.isExpired(duration, maxDuration)) {
|
||||||
|
remove(identity);
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
LOCK.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LdapUserPrincipal put(String key, LdapUserPrincipal value)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
LOCK.lock();
|
||||||
|
return super.put(key, value);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
LOCK.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authentication.validator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import org.apache.druid.java.util.common.IAE;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.apache.druid.security.basic.BasicSecurityAuthenticationException;
|
||||||
|
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@JsonTypeName("metadata")
|
||||||
|
public class MetadataStoreCredentialsValidator implements CredentialsValidator
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(MetadataStoreCredentialsValidator.class);
|
||||||
|
private final Provider<BasicAuthenticatorCacheManager> cacheManager;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public MetadataStoreCredentialsValidator(
|
||||||
|
@JacksonInject Provider<BasicAuthenticatorCacheManager> cacheManager
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public AuthenticationResult validateCredentials(
|
||||||
|
String authenticatorName,
|
||||||
|
String authorizerName,
|
||||||
|
String username,
|
||||||
|
char[] password
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthenticatorUser> userMap = cacheManager.get().getUserMap(authenticatorName);
|
||||||
|
if (userMap == null) {
|
||||||
|
throw new IAE("No userMap is available for authenticator with prefix: [%s]", authenticatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthenticatorUser user = userMap.get(username);
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BasicAuthenticatorCredentials credentials = user.getCredentials();
|
||||||
|
if (credentials == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
password,
|
||||||
|
credentials.getSalt(),
|
||||||
|
credentials.getIterations()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Arrays.equals(recalculatedHash, credentials.getHash())) {
|
||||||
|
return new AuthenticationResult(username, authorizerName, authenticatorName, null);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Password incorrect for metadata store user %s", username);
|
||||||
|
throw new BasicSecurityAuthenticationException("User metadata store authentication failed username[%s].", username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,71 +28,80 @@ import org.apache.druid.security.basic.BasicAuthDBConfig;
|
||||||
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
|
||||||
import org.apache.druid.server.security.Access;
|
import org.apache.druid.server.security.Access;
|
||||||
import org.apache.druid.server.security.Action;
|
import org.apache.druid.server.security.Action;
|
||||||
import org.apache.druid.server.security.AuthenticationResult;
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
import org.apache.druid.server.security.Authorizer;
|
import org.apache.druid.server.security.Authorizer;
|
||||||
import org.apache.druid.server.security.Resource;
|
import org.apache.druid.server.security.Resource;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@JsonTypeName("basic")
|
@JsonTypeName("basic")
|
||||||
public class BasicRoleBasedAuthorizer implements Authorizer
|
public class BasicRoleBasedAuthorizer implements Authorizer
|
||||||
{
|
{
|
||||||
private final BasicAuthorizerCacheManager cacheManager;
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final BasicAuthDBConfig dbConfig;
|
private final BasicAuthDBConfig dbConfig;
|
||||||
|
private final RoleProvider roleProvider;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public BasicRoleBasedAuthorizer(
|
public BasicRoleBasedAuthorizer(
|
||||||
@JacksonInject BasicAuthorizerCacheManager cacheManager,
|
@JacksonInject BasicAuthorizerCacheManager cacheManager,
|
||||||
@JsonProperty("name") String name,
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("initialAdminUser") String initialAdminUser,
|
||||||
|
@JsonProperty("initialAdminRole") String initialAdminRole,
|
||||||
|
@JsonProperty("initialAdminGroupMapping") String initialAdminGroupMapping,
|
||||||
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
@JsonProperty("enableCacheNotifications") Boolean enableCacheNotifications,
|
||||||
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout
|
@JsonProperty("cacheNotificationTimeout") Long cacheNotificationTimeout,
|
||||||
|
@JsonProperty("roleProvider") RoleProvider roleProvider
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.cacheManager = cacheManager;
|
|
||||||
this.dbConfig = new BasicAuthDBConfig(
|
this.dbConfig = new BasicAuthDBConfig(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
initialAdminUser,
|
||||||
|
initialAdminRole,
|
||||||
|
initialAdminGroupMapping,
|
||||||
enableCacheNotifications == null ? true : enableCacheNotifications,
|
enableCacheNotifications == null ? true : enableCacheNotifications,
|
||||||
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
cacheNotificationTimeout == null ? BasicAuthDBConfig.DEFAULT_CACHE_NOTIFY_TIMEOUT_MS : cacheNotificationTimeout,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
if (roleProvider == null) {
|
||||||
|
this.roleProvider = new MetadataStoreRoleProvider(cacheManager);
|
||||||
|
} else {
|
||||||
|
this.roleProvider = roleProvider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Access authorize(AuthenticationResult authenticationResult, Resource resource, Action action)
|
public Access authorize(AuthenticationResult authenticationResult, Resource resource, Action action)
|
||||||
{
|
{
|
||||||
if (authenticationResult == null) {
|
if (authenticationResult == null) {
|
||||||
throw new IAE("WTF? authenticationResult should never be null.");
|
throw new IAE("authenticationResult is null where it should never be.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, BasicAuthorizerUser> userMap = cacheManager.getUserMap(name);
|
Set<String> roleNames = new HashSet<>(roleProvider.getRoles(name, authenticationResult));
|
||||||
if (userMap == null) {
|
Map<String, BasicAuthorizerRole> roleMap = roleProvider.getRoleMap(name);
|
||||||
throw new IAE("Could not load userMap for authorizer [%s]", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, BasicAuthorizerRole> roleMap = cacheManager.getRoleMap(name);
|
if (roleNames.isEmpty()) {
|
||||||
|
return new Access(false);
|
||||||
|
}
|
||||||
if (roleMap == null) {
|
if (roleMap == null) {
|
||||||
throw new IAE("Could not load roleMap for authorizer [%s]", name);
|
throw new IAE("Could not load roleMap for authorizer [%s]", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
|
for (String roleName : roleNames) {
|
||||||
if (user == null) {
|
|
||||||
return new Access(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String roleName : user.getRoles()) {
|
|
||||||
BasicAuthorizerRole role = roleMap.get(roleName);
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
for (BasicAuthorizerPermission permission : role.getPermissions()) {
|
if (role != null) {
|
||||||
if (permissionCheck(resource, action, permission)) {
|
for (BasicAuthorizerPermission permission : role.getPermissions()) {
|
||||||
return new Access(true);
|
if (permissionCheck(resource, action, permission)) {
|
||||||
|
return new Access(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization;
|
||||||
|
|
||||||
|
import com.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.common.annotations.VisibleForTesting;
|
||||||
|
import org.apache.druid.java.util.common.IAE;
|
||||||
|
import org.apache.druid.java.util.common.RE;
|
||||||
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
|
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
import javax.naming.InvalidNameException;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import javax.naming.ldap.LdapName;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@JsonTypeName("ldap")
|
||||||
|
public class LDAPRoleProvider implements RoleProvider
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(LDAPRoleProvider.class);
|
||||||
|
|
||||||
|
private final BasicAuthorizerCacheManager cacheManager;
|
||||||
|
private final String[] groupFilters;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public LDAPRoleProvider(
|
||||||
|
@JacksonInject BasicAuthorizerCacheManager cacheManager,
|
||||||
|
@JsonProperty("groupFilters") String[] groupFilters
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
this.groupFilters = groupFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRoles(String authorizerPrefix, AuthenticationResult authenticationResult)
|
||||||
|
{
|
||||||
|
Set<String> roleNames = new HashSet<>();
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = cacheManager.getGroupMappingMap(authorizerPrefix);
|
||||||
|
if (groupMappingMap == null) {
|
||||||
|
throw new IAE("Could not load groupMappingMap for authorizer [%s]", authorizerPrefix);
|
||||||
|
}
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = cacheManager.getUserMap(authorizerPrefix);
|
||||||
|
if (userMap == null) {
|
||||||
|
throw new IAE("Could not load userMap for authorizer [%s]", authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the groups assigned to the LDAP user
|
||||||
|
SearchResult searchResult = Optional.ofNullable(authenticationResult.getContext())
|
||||||
|
.map(contextMap -> contextMap.get(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY))
|
||||||
|
.map(p -> {
|
||||||
|
if (p instanceof SearchResult) {
|
||||||
|
return (SearchResult) p;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.orElse(null);
|
||||||
|
if (searchResult != null) {
|
||||||
|
try {
|
||||||
|
Set<LdapName> groupNamesFromLdap = getGroupsFromLdap(searchResult);
|
||||||
|
if (groupNamesFromLdap.isEmpty()) {
|
||||||
|
LOG.debug("User %s is not mapped to any groups", authenticationResult.getIdentity());
|
||||||
|
} else {
|
||||||
|
// Get the roles mapped to LDAP groups from the metastore.
|
||||||
|
// This allows us to authorize groups LDAP user belongs
|
||||||
|
roleNames.addAll(getRoles(groupMappingMap, groupNamesFromLdap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NamingException e) {
|
||||||
|
LOG.error(e, "Exception in looking up groups for user %s", authenticationResult.getIdentity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the roles assigned to LDAP user from the metastore.
|
||||||
|
// This allow us to authorize LDAP users regardless of whether they belong to any groups or not in LDAP.
|
||||||
|
BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
|
||||||
|
if (user != null) {
|
||||||
|
roleNames.addAll(user.getRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
return roleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cacheManager.getRoleMap(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public Set<String> getRoles(Map<String, BasicAuthorizerGroupMapping> groupMappingMap, Set<LdapName> groupNamesFromLdap)
|
||||||
|
{
|
||||||
|
Set<String> roles = new HashSet<>();
|
||||||
|
|
||||||
|
if (groupMappingMap.size() == 0) {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LdapName groupName : groupNamesFromLdap) {
|
||||||
|
for (Map.Entry<String, BasicAuthorizerGroupMapping> groupMappingEntry : groupMappingMap.entrySet()) {
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = groupMappingEntry.getValue();
|
||||||
|
String mask = groupMapping.getGroupPattern();
|
||||||
|
try {
|
||||||
|
if (mask.startsWith("*,")) {
|
||||||
|
LdapName ln = new LdapName(mask.substring(2));
|
||||||
|
if (groupName.startsWith(ln)) {
|
||||||
|
roles.addAll(groupMapping.getRoles());
|
||||||
|
}
|
||||||
|
} else if (mask.endsWith(",*")) {
|
||||||
|
LdapName ln = new LdapName(mask.substring(0, mask.length() - 2));
|
||||||
|
if (groupName.endsWith(ln)) {
|
||||||
|
roles.addAll(groupMapping.getRoles());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LdapName ln = new LdapName(mask);
|
||||||
|
if (groupName.equals(ln)) {
|
||||||
|
roles.addAll(groupMapping.getRoles());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidNameException e) {
|
||||||
|
throw new RuntimeException(String.format(Locale.getDefault(),
|
||||||
|
"Configuration problem - Invalid groupMapping '%s'", mask));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<LdapName> getGroupsFromLdap(SearchResult userResult) throws NamingException
|
||||||
|
{
|
||||||
|
Set<LdapName> groups = new TreeSet<>();
|
||||||
|
|
||||||
|
Attribute memberOf = userResult.getAttributes().get("memberOf");
|
||||||
|
if (memberOf == null) {
|
||||||
|
LOG.debug("No memberOf attributes");
|
||||||
|
return groups; // not part of any groups
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < memberOf.size(); i++) {
|
||||||
|
String memberDn = memberOf.get(i).toString();
|
||||||
|
LdapName ln;
|
||||||
|
try {
|
||||||
|
ln = new LdapName(memberDn);
|
||||||
|
}
|
||||||
|
catch (InvalidNameException e) {
|
||||||
|
LOG.debug("Invalid LDAP name: %s", memberDn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this.groupFilters != null) {
|
||||||
|
if (allowedLdapGroup(ln, new TreeSet<>(Arrays.asList(this.groupFilters)))) {
|
||||||
|
groups.add(ln);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
groups.add(ln);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allowedLdapGroup(LdapName groupName, Set<String> groupFilters)
|
||||||
|
{
|
||||||
|
for (String filter : groupFilters) {
|
||||||
|
try {
|
||||||
|
if (filter.startsWith("*,")) {
|
||||||
|
LdapName ln = new LdapName(filter.substring(2));
|
||||||
|
if (groupName.startsWith(ln)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (filter.endsWith(",*")) {
|
||||||
|
LdapName ln = new LdapName(filter.substring(0, filter.length() - 2));
|
||||||
|
if (groupName.endsWith(ln)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug("Attempting exact filter %s", filter);
|
||||||
|
LdapName ln = new LdapName(filter);
|
||||||
|
if (groupName.equals(ln)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidNameException e) {
|
||||||
|
throw new RE(StringUtils.format("Configuration problem - Invalid groupFilter '%s'", filter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
|
import org.apache.druid.java.util.common.IAE;
|
||||||
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
|
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@JsonTypeName("metadata")
|
||||||
|
public class MetadataStoreRoleProvider implements RoleProvider
|
||||||
|
{
|
||||||
|
private static final Logger LOG = new Logger(MetadataStoreRoleProvider.class);
|
||||||
|
private final BasicAuthorizerCacheManager cacheManager;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public MetadataStoreRoleProvider(
|
||||||
|
@JacksonInject BasicAuthorizerCacheManager cacheManager
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRoles(String authorizerPrefix, AuthenticationResult authenticationResult)
|
||||||
|
{
|
||||||
|
Set<String> roleNames = new HashSet<>();
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = cacheManager.getUserMap(authorizerPrefix);
|
||||||
|
if (userMap == null) {
|
||||||
|
throw new IAE("Could not load userMap for authorizer [%s]", authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerUser user = userMap.get(authenticationResult.getIdentity());
|
||||||
|
if (user != null) {
|
||||||
|
roleNames.addAll(user.getRoles());
|
||||||
|
}
|
||||||
|
return roleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cacheManager.getRoleMap(authorizerPrefix);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = MetadataStoreRoleProvider.class)
|
||||||
|
@JsonSubTypes(value = {
|
||||||
|
@JsonSubTypes.Type(name = "metadata", value = MetadataStoreRoleProvider.class),
|
||||||
|
@JsonSubTypes.Type(name = "ldap", value = LDAPRoleProvider.class),
|
||||||
|
})
|
||||||
|
public interface RoleProvider
|
||||||
|
{
|
||||||
|
Set<String> getRoles(String authorizerPrefix, AuthenticationResult authenticationResult);
|
||||||
|
Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix);
|
||||||
|
}
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.druid.security.basic.authorization.db.cache;
|
package org.apache.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
|
||||||
|
@ -32,11 +33,18 @@ public interface BasicAuthorizerCacheManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Update this cache manager's local state with fresh information pushed by the coordinator.
|
* 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 authorizerPrefix The name of the authorizer this update applies to.
|
||||||
* @param serializedUserAndRoleMap The updated, serialized user and role maps
|
* @param serializedUserAndRoleMap The updated, serialized user and role maps
|
||||||
*/
|
*/
|
||||||
void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap);
|
void handleAuthorizerUserUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 serializedGroupMappingAndRoleMap The updated, serialized group and role maps
|
||||||
|
* */
|
||||||
|
void handleAuthorizerGroupMappingUpdate(String authorizerPrefix, byte[] serializedGroupMappingAndRoleMap);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the cache manager's local view of the user map for the authorizer named `authorizerPrefix`.
|
* Return the cache manager's local view of the user map for the authorizer named `authorizerPrefix`.
|
||||||
|
@ -53,4 +61,20 @@ public interface BasicAuthorizerCacheManager
|
||||||
* @return Role map
|
* @return Role map
|
||||||
*/
|
*/
|
||||||
Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix);
|
Map<String, BasicAuthorizerRole> getRoleMap(String authorizerPrefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cache manager's local view of the groupMapping map for the authorizer named `authorizerPrefix`.
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix The name of the authorizer
|
||||||
|
* @return GroupMapping map
|
||||||
|
*/
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> getGroupMappingMap(String authorizerPrefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the cache manager's local view of the groupMapping-role map for the authorizer named `authorizerPrefix`.
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix The name of the authorizer
|
||||||
|
* @return Role map
|
||||||
|
*/
|
||||||
|
Map<String, BasicAuthorizerRole> getGroupMappingRoleMap(String authorizerPrefix);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,15 @@ public interface BasicAuthorizerCacheNotifier
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Send the user map state contained in updatedUserMap to all non-coordinator Druid services
|
* Send the user map state contained in updatedUserMap to all non-coordinator Druid services
|
||||||
*
|
* @param authorizerPrefix Name of authorizer being updated
|
||||||
* @param authorizerPrefix Name of authorizer being updated
|
|
||||||
* @param userAndRoleMap User/role map state
|
* @param userAndRoleMap User/role map state
|
||||||
*/
|
*/
|
||||||
void addUpdate(String authorizerPrefix, byte[] userAndRoleMap);
|
void addUpdateUser(String authorizerPrefix, byte[] userAndRoleMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the groupMapping map state contained in updatedGroupMappingMap to all non-coordinator Druid services
|
||||||
|
* @param authorizerPrefix Name of authorizer being updated
|
||||||
|
* @param groupMappingAndRoleMap Group/role map state
|
||||||
|
*/
|
||||||
|
void addUpdateGroupMapping(String authorizerPrefix, byte[] groupMappingAndRoleMap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerC
|
||||||
{
|
{
|
||||||
|
|
||||||
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
private final LifecycleLock lifecycleLock = new LifecycleLock();
|
||||||
private CommonCacheNotifier cacheNotifier;
|
private final CommonCacheNotifier cacheUserNotifier;
|
||||||
|
private final CommonCacheNotifier cacheGroupMappingNotifier;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoordinatorBasicAuthorizerCacheNotifier(
|
public CoordinatorBasicAuthorizerCacheNotifier(
|
||||||
|
@ -53,11 +54,18 @@ public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerC
|
||||||
@EscalatedClient HttpClient httpClient
|
@EscalatedClient HttpClient httpClient
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
cacheNotifier = new CommonCacheNotifier(
|
cacheUserNotifier = new CommonCacheNotifier(
|
||||||
getAuthorizerConfigMap(authorizerMapper),
|
getAuthorizerConfigMap(authorizerMapper),
|
||||||
discoveryProvider,
|
discoveryProvider,
|
||||||
httpClient,
|
httpClient,
|
||||||
"/druid-ext/basic-security/authorization/listen/%s",
|
"/druid-ext/basic-security/authorization/listen/users/%s",
|
||||||
|
"CoordinatorBasicAuthorizerCacheNotifier"
|
||||||
|
);
|
||||||
|
cacheGroupMappingNotifier = new CommonCacheNotifier(
|
||||||
|
getAuthorizerConfigMap(authorizerMapper),
|
||||||
|
discoveryProvider,
|
||||||
|
httpClient,
|
||||||
|
"/druid-ext/basic-security/authorization/listen/groupMappings/%s",
|
||||||
"CoordinatorBasicAuthorizerCacheNotifier"
|
"CoordinatorBasicAuthorizerCacheNotifier"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +78,8 @@ public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerC
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cacheNotifier.start();
|
cacheUserNotifier.start();
|
||||||
|
cacheGroupMappingNotifier.start();
|
||||||
lifecycleLock.started();
|
lifecycleLock.started();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -85,7 +94,8 @@ public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerC
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
cacheNotifier.stop();
|
cacheUserNotifier.stop();
|
||||||
|
cacheGroupMappingNotifier.stop();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
lifecycleLock.exitStop();
|
lifecycleLock.exitStop();
|
||||||
|
@ -93,10 +103,17 @@ public class CoordinatorBasicAuthorizerCacheNotifier implements BasicAuthorizerC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String updatedAuthorizerPrefix, byte[] updatedUserMap)
|
public void addUpdateUser(String updatedAuthorizerPrefix, byte[] userAndRoleMap)
|
||||||
{
|
{
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
cacheNotifier.addUpdate(updatedAuthorizerPrefix, updatedUserMap);
|
cacheUserNotifier.addUpdate(updatedAuthorizerPrefix, userAndRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUpdateGroupMapping(String updatedAuthorizerPrefix, byte[] groupMappingAndRoleMap)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
cacheGroupMappingNotifier.addUpdate(updatedAuthorizerPrefix, groupMappingAndRoleMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, BasicAuthDBConfig> getAuthorizerConfigMap(AuthorizerMapper mapper)
|
private Map<String, BasicAuthDBConfig> getAuthorizerConfigMap(AuthorizerMapper mapper)
|
||||||
|
|
|
@ -43,8 +43,10 @@ import org.apache.druid.java.util.http.client.response.BytesFullResponseHolder;
|
||||||
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
import org.apache.druid.security.basic.BasicAuthUtils;
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
|
||||||
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
import org.apache.druid.server.security.Authorizer;
|
import org.apache.druid.server.security.Authorizer;
|
||||||
import org.apache.druid.server.security.AuthorizerMapper;
|
import org.apache.druid.server.security.AuthorizerMapper;
|
||||||
|
@ -69,6 +71,8 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
|
|
||||||
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerUser>> cachedUserMaps;
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerUser>> cachedUserMaps;
|
||||||
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerRole>> cachedRoleMaps;
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerRole>> cachedRoleMaps;
|
||||||
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerGroupMapping>> cachedGroupMappingMaps;
|
||||||
|
private final ConcurrentHashMap<String, Map<String, BasicAuthorizerRole>> cachedGroupMappingRoleMaps;
|
||||||
|
|
||||||
private final Set<String> authorizerPrefixes;
|
private final Set<String> authorizerPrefixes;
|
||||||
private final Injector injector;
|
private final Injector injector;
|
||||||
|
@ -92,6 +96,8 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.cachedUserMaps = new ConcurrentHashMap<>();
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
||||||
|
this.cachedGroupMappingMaps = new ConcurrentHashMap<>();
|
||||||
|
this.cachedGroupMappingRoleMaps = new ConcurrentHashMap<>();
|
||||||
this.authorizerPrefixes = new HashSet<>();
|
this.authorizerPrefixes = new HashSet<>();
|
||||||
this.druidLeaderClient = druidLeaderClient;
|
this.druidLeaderClient = druidLeaderClient;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +124,7 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
LOG.debug("Inserting random polling delay of [%s] ms", randomDelay);
|
LOG.debug("Inserting random polling delay of [%s] ms", randomDelay);
|
||||||
Thread.sleep(randomDelay);
|
Thread.sleep(randomDelay);
|
||||||
|
|
||||||
LOG.debug("Scheduled cache poll is running");
|
LOG.debug("Scheduled userMap cache poll is running");
|
||||||
for (String authorizerPrefix : authorizerPrefixes) {
|
for (String authorizerPrefix : authorizerPrefixes) {
|
||||||
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerPrefix, false);
|
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerPrefix, false);
|
||||||
if (userAndRoleMap != null) {
|
if (userAndRoleMap != null) {
|
||||||
|
@ -126,7 +132,7 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Scheduled cache poll is done");
|
LOG.debug("Scheduled userMap cache poll is done");
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
||||||
|
@ -134,6 +140,32 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 groupMappingMap cache poll is running");
|
||||||
|
for (String authorizerPrefix : authorizerPrefixes) {
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = fetchGroupAndRoleMapFromCoordinator(authorizerPrefix, false);
|
||||||
|
if (groupMappingAndRoleMap != null) {
|
||||||
|
cachedGroupMappingMaps.put(authorizerPrefix, groupMappingAndRoleMap.getGroupMappingMap());
|
||||||
|
cachedGroupMappingRoleMaps.put(authorizerPrefix, groupMappingAndRoleMap.getRoleMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Scheduled groupMappingMap cache poll is done");
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedGroupMappingMaps.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
lifecycleLock.started();
|
lifecycleLock.started();
|
||||||
LOG.info("Started CoordinatorPollingBasicAuthorizerCacheManager.");
|
LOG.info("Started CoordinatorPollingBasicAuthorizerCacheManager.");
|
||||||
}
|
}
|
||||||
|
@ -155,9 +187,9 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
public void handleAuthorizerUserUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
||||||
{
|
{
|
||||||
LOG.debug("Received cache update for authorizer [%s].", authorizerPrefix);
|
LOG.debug("Received userMap cache update for authorizer [%s].", authorizerPrefix);
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
try {
|
try {
|
||||||
UserAndRoleMap userAndRoleMap = objectMapper.readValue(
|
UserAndRoleMap userAndRoleMap = objectMapper.readValue(
|
||||||
|
@ -169,7 +201,7 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
cachedRoleMaps.put(authorizerPrefix, userAndRoleMap.getRoleMap());
|
||||||
|
|
||||||
if (commonCacheConfig.getCacheDirectory() != null) {
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
writeMapToDisk(authorizerPrefix, serializedUserAndRoleMap);
|
writeUserMapToDisk(authorizerPrefix, serializedUserAndRoleMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
@ -177,6 +209,29 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthorizerGroupMappingUpdate(String authorizerPrefix, byte[] serializedGroupMappingAndRoleMap)
|
||||||
|
{
|
||||||
|
LOG.debug("Received groupMappingMap cache update for authorizer [%s].", authorizerPrefix);
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
try {
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = objectMapper.readValue(
|
||||||
|
serializedGroupMappingAndRoleMap,
|
||||||
|
BasicAuthUtils.AUTHORIZER_GROUP_MAPPING_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
|
||||||
|
cachedGroupMappingMaps.put(authorizerPrefix, groupMappingAndRoleMap.getGroupMappingMap());
|
||||||
|
cachedGroupMappingRoleMaps.put(authorizerPrefix, groupMappingAndRoleMap.getRoleMap());
|
||||||
|
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeGroupMappingMapToDisk(authorizerPrefix, serializedGroupMappingAndRoleMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "Could not deserialize groupMapping/role map received from coordinator.").emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, BasicAuthorizerUser> getUserMap(String authorizerPrefix)
|
public Map<String, BasicAuthorizerUser> getUserMap(String authorizerPrefix)
|
||||||
{
|
{
|
||||||
|
@ -189,9 +244,26 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
return cachedRoleMaps.get(authorizerPrefix);
|
return cachedRoleMaps.get(authorizerPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getGroupMappingMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cachedGroupMappingMaps.get(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getGroupMappingRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return cachedGroupMappingRoleMaps.get(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
private String getUserRoleMapFilename(String prefix)
|
private String getUserRoleMapFilename(String prefix)
|
||||||
{
|
{
|
||||||
return StringUtils.format("%s.authorizer.cache", prefix);
|
return StringUtils.format("%s.authorizer.userRole.cache", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getGroupMappingRoleMapFilename(String prefix)
|
||||||
|
{
|
||||||
|
return StringUtils.format("%s.authorizer.groupMappingRole.cache", prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -207,7 +279,20 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeMapToDisk(String prefix, byte[] userMapBytes) throws IOException
|
@Nullable
|
||||||
|
private GroupMappingAndRoleMap loadGroupMappingAndRoleMapFromDisk(String prefix) throws IOException
|
||||||
|
{
|
||||||
|
File groupMappingAndRoleMapFile = new File(commonCacheConfig.getCacheDirectory(), getGroupMappingRoleMapFilename(prefix));
|
||||||
|
if (!groupMappingAndRoleMapFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(
|
||||||
|
groupMappingAndRoleMapFile,
|
||||||
|
BasicAuthUtils.AUTHORIZER_GROUP_MAPPING_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeUserMapToDisk(String prefix, byte[] userMapBytes) throws IOException
|
||||||
{
|
{
|
||||||
File cacheDir = new File(commonCacheConfig.getCacheDirectory());
|
File cacheDir = new File(commonCacheConfig.getCacheDirectory());
|
||||||
cacheDir.mkdirs();
|
cacheDir.mkdirs();
|
||||||
|
@ -221,13 +306,27 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeGroupMappingMapToDisk(String prefix, byte[] groupMappingBytes) throws IOException
|
||||||
|
{
|
||||||
|
File cacheDir = new File(commonCacheConfig.getCacheDirectory());
|
||||||
|
cacheDir.mkdirs();
|
||||||
|
File groupMapFile = new File(commonCacheConfig.getCacheDirectory(), getGroupMappingRoleMapFilename(prefix));
|
||||||
|
FileUtils.writeAtomically(
|
||||||
|
groupMapFile,
|
||||||
|
out -> {
|
||||||
|
out.write(groupMappingBytes);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private UserAndRoleMap fetchUserAndRoleMapFromCoordinator(String prefix, boolean isInit)
|
private UserAndRoleMap fetchUserAndRoleMapFromCoordinator(String prefix, boolean isInit)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return RetryUtils.retry(
|
return RetryUtils.retry(
|
||||||
() -> {
|
() -> {
|
||||||
return tryFetchMapsFromCoordinator(prefix);
|
return tryFetchUserMapsFromCoordinator(prefix);
|
||||||
},
|
},
|
||||||
e -> true,
|
e -> true,
|
||||||
commonCacheConfig.getMaxSyncRetries()
|
commonCacheConfig.getMaxSyncRetries()
|
||||||
|
@ -252,7 +351,38 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserAndRoleMap tryFetchMapsFromCoordinator(
|
@Nullable
|
||||||
|
private GroupMappingAndRoleMap fetchGroupAndRoleMapFromCoordinator(String prefix, boolean isInit)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return RetryUtils.retry(
|
||||||
|
() -> {
|
||||||
|
return tryFetchGroupMappingMapsFromCoordinator(prefix);
|
||||||
|
},
|
||||||
|
e -> true,
|
||||||
|
commonCacheConfig.getMaxSyncRetries()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.makeAlert(e, "Encountered exception while fetching group and role map for authorizer [%s]", prefix).emit();
|
||||||
|
if (isInit) {
|
||||||
|
if (commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
try {
|
||||||
|
LOG.info("Attempting to load group map snapshot from disk.");
|
||||||
|
return loadGroupMappingAndRoleMapFromDisk(prefix);
|
||||||
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
e2.addSuppressed(e);
|
||||||
|
LOG.makeAlert(e2, "Encountered exception while loading group-role map snapshot for authorizer [%s]", prefix)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserAndRoleMap tryFetchUserMapsFromCoordinator(
|
||||||
String prefix
|
String prefix
|
||||||
) throws Exception
|
) throws Exception
|
||||||
{
|
{
|
||||||
|
@ -271,11 +401,35 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
BasicAuthUtils.AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE
|
BasicAuthUtils.AUTHORIZER_USER_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
);
|
);
|
||||||
if (userAndRoleMap != null && commonCacheConfig.getCacheDirectory() != null) {
|
if (userAndRoleMap != null && commonCacheConfig.getCacheDirectory() != null) {
|
||||||
writeMapToDisk(prefix, userRoleMapBytes);
|
writeUserMapToDisk(prefix, userRoleMapBytes);
|
||||||
}
|
}
|
||||||
return userAndRoleMap;
|
return userAndRoleMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GroupMappingAndRoleMap tryFetchGroupMappingMapsFromCoordinator(
|
||||||
|
String prefix
|
||||||
|
) throws Exception
|
||||||
|
{
|
||||||
|
Request req = druidLeaderClient.makeRequest(
|
||||||
|
HttpMethod.GET,
|
||||||
|
StringUtils.format("/druid-ext/basic-security/authorization/db/%s/cachedSerializedGroupMappingMap", prefix)
|
||||||
|
);
|
||||||
|
BytesFullResponseHolder responseHolder = druidLeaderClient.go(
|
||||||
|
req,
|
||||||
|
new BytesFullResponseHandler()
|
||||||
|
);
|
||||||
|
byte[] groupRoleMapBytes = responseHolder.getContent();
|
||||||
|
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = objectMapper.readValue(
|
||||||
|
groupRoleMapBytes,
|
||||||
|
BasicAuthUtils.AUTHORIZER_GROUP_MAPPING_AND_ROLE_MAP_TYPE_REFERENCE
|
||||||
|
);
|
||||||
|
if (groupMappingAndRoleMap != null && commonCacheConfig.getCacheDirectory() != null) {
|
||||||
|
writeGroupMappingMapToDisk(prefix, groupRoleMapBytes);
|
||||||
|
}
|
||||||
|
return groupMappingAndRoleMap;
|
||||||
|
}
|
||||||
|
|
||||||
private void initUserMaps()
|
private void initUserMaps()
|
||||||
{
|
{
|
||||||
AuthorizerMapper authorizerMapper = injector.getInstance(AuthorizerMapper.class);
|
AuthorizerMapper authorizerMapper = injector.getInstance(AuthorizerMapper.class);
|
||||||
|
@ -289,11 +443,18 @@ public class CoordinatorPollingBasicAuthorizerCacheManager implements BasicAutho
|
||||||
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
String authorizerName = entry.getKey();
|
String authorizerName = entry.getKey();
|
||||||
authorizerPrefixes.add(authorizerName);
|
authorizerPrefixes.add(authorizerName);
|
||||||
|
|
||||||
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerName, true);
|
UserAndRoleMap userAndRoleMap = fetchUserAndRoleMapFromCoordinator(authorizerName, true);
|
||||||
if (userAndRoleMap != null) {
|
if (userAndRoleMap != null) {
|
||||||
cachedUserMaps.put(authorizerName, userAndRoleMap.getUserMap());
|
cachedUserMaps.put(authorizerName, userAndRoleMap.getUserMap());
|
||||||
cachedRoleMaps.put(authorizerName, userAndRoleMap.getRoleMap());
|
cachedRoleMaps.put(authorizerName, userAndRoleMap.getRoleMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = fetchGroupAndRoleMapFromCoordinator(authorizerName, true);
|
||||||
|
if (groupMappingAndRoleMap != null) {
|
||||||
|
cachedGroupMappingMaps.put(authorizerName, groupMappingAndRoleMap.getGroupMappingMap());
|
||||||
|
cachedGroupMappingRoleMaps.put(authorizerName, groupMappingAndRoleMap.getRoleMap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.apache.druid.security.basic.authorization.db.cache;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import org.apache.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
import org.apache.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
|
||||||
|
@ -39,7 +40,13 @@ public class MetadataStoragePollingBasicAuthorizerCacheManager implements BasicA
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAuthorizerUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
public void handleAuthorizerUserUpdate(String authorizerPrefix, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleAuthorizerGroupMappingUpdate(String authorizerPrefix, byte[] serializedGroupMappingAndRoleMap)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -55,4 +62,16 @@ public class MetadataStoragePollingBasicAuthorizerCacheManager implements BasicA
|
||||||
{
|
{
|
||||||
return storageUpdater.getCachedRoleMap(authorizerPrefix);
|
return storageUpdater.getCachedRoleMap(authorizerPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getGroupMappingMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return storageUpdater.getCachedGroupMappingMap(authorizerPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerRole> getGroupMappingRoleMap(String authorizerPrefix)
|
||||||
|
{
|
||||||
|
return getRoleMap(authorizerPrefix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,26 @@ package org.apache.druid.security.basic.authorization.db.cache;
|
||||||
*/
|
*/
|
||||||
public class NoopBasicAuthorizerCacheNotifier implements BasicAuthorizerCacheNotifier
|
public class NoopBasicAuthorizerCacheNotifier implements 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
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String authorizerPrefix, byte[] userAndRoleMap)
|
public void addUpdateUser(String authorizerPrefix, byte[] userAndRoleMap)
|
||||||
|
{
|
||||||
|
// Do nothing as this is a noop implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the groupMapping map state contained in updatedGroupMappingMap to all non-coordinator Druid services
|
||||||
|
*
|
||||||
|
* @param authorizerPrefix Name of authorizer being updated
|
||||||
|
* @param groupMappingAndRoleMap Group/role map state
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addUpdateGroupMapping(String authorizerPrefix, byte[] groupMappingAndRoleMap)
|
||||||
{
|
{
|
||||||
// Do nothing as this is a noop implementation
|
// Do nothing as this is a noop implementation
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.druid.security.basic.authorization.db.updater;
|
package org.apache.druid.security.basic.authorization.db.updater;
|
||||||
|
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
import org.apache.druid.server.security.ResourceAction;
|
import org.apache.druid.server.security.ResourceAction;
|
||||||
|
@ -38,22 +39,34 @@ public interface BasicAuthorizerMetadataStorageUpdater
|
||||||
|
|
||||||
void deleteUser(String prefix, String userName);
|
void deleteUser(String prefix, String userName);
|
||||||
|
|
||||||
|
void createGroupMapping(String prefix, BasicAuthorizerGroupMapping groupMapping);
|
||||||
|
|
||||||
|
void deleteGroupMapping(String prefix, String groupMappingName);
|
||||||
|
|
||||||
void createRole(String prefix, String roleName);
|
void createRole(String prefix, String roleName);
|
||||||
|
|
||||||
void deleteRole(String prefix, String roleName);
|
void deleteRole(String prefix, String roleName);
|
||||||
|
|
||||||
void assignRole(String prefix, String userName, String roleName);
|
void assignUserRole(String prefix, String userName, String roleName);
|
||||||
|
|
||||||
void unassignRole(String prefix, String userName, String roleName);
|
void unassignUserRole(String prefix, String userName, String roleName);
|
||||||
|
|
||||||
|
void assignGroupMappingRole(String prefix, String groupMappingName, String roleName);
|
||||||
|
|
||||||
|
void unassignGroupMappingRole(String prefix, String groupMappingName, String roleName);
|
||||||
|
|
||||||
void setPermissions(String prefix, String roleName, List<ResourceAction> permissions);
|
void setPermissions(String prefix, String roleName, List<ResourceAction> permissions);
|
||||||
|
|
||||||
Map<String, BasicAuthorizerUser> getCachedUserMap(String prefix);
|
Map<String, BasicAuthorizerUser> getCachedUserMap(String prefix);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> getCachedGroupMappingMap(String prefix);
|
||||||
|
|
||||||
Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix);
|
Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix);
|
||||||
|
|
||||||
byte[] getCurrentUserMapBytes(String prefix);
|
byte[] getCurrentUserMapBytes(String prefix);
|
||||||
|
|
||||||
|
byte[] getCurrentGroupMappingMapBytes(String prefix);
|
||||||
|
|
||||||
byte[] getCurrentRoleMapBytes(String prefix);
|
byte[] getCurrentRoleMapBytes(String prefix);
|
||||||
|
|
||||||
void refreshAllNotification();
|
void refreshAllNotification();
|
||||||
|
|
|
@ -38,15 +38,19 @@ import org.apache.druid.metadata.MetadataCASUpdate;
|
||||||
import org.apache.druid.metadata.MetadataStorageConnector;
|
import org.apache.druid.metadata.MetadataStorageConnector;
|
||||||
import org.apache.druid.metadata.MetadataStorageTablesConfig;
|
import org.apache.druid.metadata.MetadataStorageTablesConfig;
|
||||||
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthDBConfig;
|
||||||
import org.apache.druid.security.basic.BasicAuthUtils;
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
|
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMappingMapBundle;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleMapBundle;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleMapBundle;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserMapBundle;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserMapBundle;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
|
||||||
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
import org.apache.druid.server.security.Action;
|
import org.apache.druid.server.security.Action;
|
||||||
import org.apache.druid.server.security.Authorizer;
|
import org.apache.druid.server.security.Authorizer;
|
||||||
|
@ -56,14 +60,15 @@ import org.apache.druid.server.security.ResourceAction;
|
||||||
import org.apache.druid.server.security.ResourceType;
|
import org.apache.druid.server.security.ResourceType;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
@ -78,6 +83,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
private static final long UPDATE_RETRY_DELAY = 1000;
|
private static final long UPDATE_RETRY_DELAY = 1000;
|
||||||
|
|
||||||
private static final String USERS = "users";
|
private static final String USERS = "users";
|
||||||
|
private static final String GROUP_MAPPINGS = "groupMappings";
|
||||||
private static final String ROLES = "roles";
|
private static final String ROLES = "roles";
|
||||||
|
|
||||||
public static final List<ResourceAction> SUPERUSER_PERMISSIONS = makeSuperUserPermissions();
|
public static final List<ResourceAction> SUPERUSER_PERMISSIONS = makeSuperUserPermissions();
|
||||||
|
@ -91,6 +97,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
private final int numRetries = 5;
|
private final int numRetries = 5;
|
||||||
|
|
||||||
private final Map<String, BasicAuthorizerUserMapBundle> cachedUserMaps;
|
private final Map<String, BasicAuthorizerUserMapBundle> cachedUserMaps;
|
||||||
|
private final Map<String, BasicAuthorizerGroupMappingMapBundle> cachedGroupMappingMaps;
|
||||||
private final Map<String, BasicAuthorizerRoleMapBundle> cachedRoleMaps;
|
private final Map<String, BasicAuthorizerRoleMapBundle> cachedRoleMaps;
|
||||||
|
|
||||||
private final Set<String> authorizerNames;
|
private final Set<String> authorizerNames;
|
||||||
|
@ -118,6 +125,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.cacheNotifier = cacheNotifier;
|
this.cacheNotifier = cacheNotifier;
|
||||||
this.cachedUserMaps = new ConcurrentHashMap<>();
|
this.cachedUserMaps = new ConcurrentHashMap<>();
|
||||||
|
this.cachedGroupMappingMaps = new ConcurrentHashMap<>();
|
||||||
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
this.cachedRoleMaps = new ConcurrentHashMap<>();
|
||||||
this.authorizerNames = new HashSet<>();
|
this.authorizerNames = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
@ -138,6 +146,8 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
for (Map.Entry<String, Authorizer> entry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
for (Map.Entry<String, Authorizer> entry : authorizerMapper.getAuthorizerMap().entrySet()) {
|
||||||
Authorizer authorizer = entry.getValue();
|
Authorizer authorizer = entry.getValue();
|
||||||
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
if (authorizer instanceof BasicRoleBasedAuthorizer) {
|
||||||
|
BasicRoleBasedAuthorizer basicRoleBasedAuthorizer = (BasicRoleBasedAuthorizer) authorizer;
|
||||||
|
BasicAuthDBConfig dbConfig = basicRoleBasedAuthorizer.getDbConfig();
|
||||||
String authorizerName = entry.getKey();
|
String authorizerName = entry.getKey();
|
||||||
authorizerNames.add(authorizerName);
|
authorizerNames.add(authorizerName);
|
||||||
|
|
||||||
|
@ -148,6 +158,13 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
);
|
);
|
||||||
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
||||||
|
|
||||||
|
byte[] groupMappingMapBytes = getCurrentGroupMappingMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
groupMappingMapBytes
|
||||||
|
);
|
||||||
|
cachedGroupMappingMaps.put(authorizerName, new BasicAuthorizerGroupMappingMapBundle(groupMappingMap, groupMappingMapBytes));
|
||||||
|
|
||||||
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
||||||
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
|
@ -155,7 +172,11 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
);
|
);
|
||||||
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
||||||
|
|
||||||
initSuperusers(authorizerName, userMap, roleMap);
|
initSuperUsersAndGroupMapping(authorizerName, userMap, roleMap, groupMappingMap,
|
||||||
|
dbConfig.getInitialAdminUser(),
|
||||||
|
dbConfig.getInitialAdminRole(),
|
||||||
|
dbConfig.getInitialAdminGroupMapping()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,47 +184,53 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
exec,
|
exec,
|
||||||
new Duration(commonCacheConfig.getPollingPeriod()),
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
new Duration(commonCacheConfig.getPollingPeriod()),
|
new Duration(commonCacheConfig.getPollingPeriod()),
|
||||||
new Callable<ScheduledExecutors.Signal>()
|
() -> {
|
||||||
{
|
if (stopped) {
|
||||||
@Override
|
return ScheduledExecutors.Signal.STOP;
|
||||||
public ScheduledExecutors.Signal call()
|
}
|
||||||
{
|
try {
|
||||||
if (stopped) {
|
LOG.debug("Scheduled db poll is running");
|
||||||
return ScheduledExecutors.Signal.STOP;
|
for (String authorizerName : authorizerNames) {
|
||||||
}
|
|
||||||
try {
|
|
||||||
LOG.debug("Scheduled db poll is running");
|
|
||||||
for (String authorizerName : authorizerNames) {
|
|
||||||
|
|
||||||
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
|
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
|
||||||
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
userMapBytes
|
userMapBytes
|
||||||
);
|
);
|
||||||
if (userMapBytes != null) {
|
if (userMapBytes != null) {
|
||||||
synchronized (cachedUserMaps) {
|
synchronized (cachedUserMaps) {
|
||||||
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
byte[] groupMappingMapBytes = getCurrentGroupMappingMapBytes(authorizerName);
|
||||||
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
roleMapBytes
|
groupMappingMapBytes
|
||||||
);
|
);
|
||||||
if (roleMapBytes != null) {
|
if (groupMappingMapBytes != null) {
|
||||||
synchronized (cachedUserMaps) {
|
synchronized (cachedGroupMappingMaps) {
|
||||||
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
cachedGroupMappingMaps.put(authorizerName, new BasicAuthorizerGroupMappingMapBundle(groupMappingMap, groupMappingMapBytes));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
roleMapBytes
|
||||||
|
);
|
||||||
|
if (roleMapBytes != null) {
|
||||||
|
synchronized (cachedRoleMaps) {
|
||||||
|
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG.debug("Scheduled db poll is done");
|
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
LOG.debug("Scheduled db poll is done");
|
||||||
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps.").emit();
|
|
||||||
}
|
|
||||||
return ScheduledExecutors.Signal.REPEAT;
|
|
||||||
}
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps, cachedGroupMappingMaps, cachedRoleMaps.").emit();
|
||||||
|
}
|
||||||
|
return ScheduledExecutors.Signal.REPEAT;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -239,7 +266,69 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
byte[] newUserMapValue
|
byte[] newUserMapValue
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return tryUpdateUserAndRoleMap(prefix, userMap, oldUserMapValue, newUserMapValue, null, null, null);
|
try {
|
||||||
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
|
if (userMap != null) {
|
||||||
|
updates.add(
|
||||||
|
createMetadataCASUpdate(prefix, oldUserMapValue, newUserMapValue, USERS)
|
||||||
|
);
|
||||||
|
|
||||||
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
|
if (succeeded) {
|
||||||
|
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
|
||||||
|
|
||||||
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateGroupMappingMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
|
||||||
|
byte[] oldGroupMappingMapValue,
|
||||||
|
byte[] newGroupMappingMapValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
|
if (groupMappingMap != null) {
|
||||||
|
updates.add(
|
||||||
|
createMetadataCASUpdate(prefix, oldGroupMappingMapValue, newGroupMappingMapValue, GROUP_MAPPINGS)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
|
if (succeeded) {
|
||||||
|
cachedGroupMappingMaps.put(prefix,
|
||||||
|
new BasicAuthorizerGroupMappingMapBundle(
|
||||||
|
groupMappingMap,
|
||||||
|
newGroupMappingMapValue
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryUpdateRoleMap(
|
private boolean tryUpdateRoleMap(
|
||||||
|
@ -249,7 +338,33 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
byte[] newRoleMapValue
|
byte[] newRoleMapValue
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return tryUpdateUserAndRoleMap(prefix, null, null, null, roleMap, oldRoleMapValue, newRoleMapValue);
|
try {
|
||||||
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
|
if (roleMap != null) {
|
||||||
|
updates.add(
|
||||||
|
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
|
||||||
|
);
|
||||||
|
|
||||||
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
|
if (succeeded) {
|
||||||
|
|
||||||
|
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
|
||||||
|
|
||||||
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
|
||||||
|
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryUpdateUserAndRoleMap(
|
private boolean tryUpdateUserAndRoleMap(
|
||||||
|
@ -264,55 +379,91 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
List<MetadataCASUpdate> updates = new ArrayList<>();
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
if (userMap != null) {
|
if (userMap != null && roleMap != null) {
|
||||||
updates.add(
|
updates.add(
|
||||||
new MetadataCASUpdate(
|
createMetadataCASUpdate(prefix, oldUserMapValue, newUserMapValue, USERS)
|
||||||
connectorConfig.getConfigTable(),
|
|
||||||
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
|
||||||
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
|
||||||
getPrefixedKeyColumn(prefix, USERS),
|
|
||||||
oldUserMapValue,
|
|
||||||
newUserMapValue
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (roleMap != null) {
|
|
||||||
updates.add(
|
updates.add(
|
||||||
new MetadataCASUpdate(
|
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
|
||||||
connectorConfig.getConfigTable(),
|
);
|
||||||
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
|
||||||
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
getPrefixedKeyColumn(prefix, ROLES),
|
if (succeeded) {
|
||||||
oldRoleMapValue,
|
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
|
||||||
newRoleMapValue
|
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
|
||||||
)
|
|
||||||
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
|
||||||
|
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryUpdateGroupMappingAndRoleMap(
|
||||||
|
String prefix,
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
|
||||||
|
byte[] oldGroupMappingMapValue,
|
||||||
|
byte[] newGroupMappingMapValue,
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap,
|
||||||
|
byte[] oldRoleMapValue,
|
||||||
|
byte[] newRoleMapValue
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
List<MetadataCASUpdate> updates = new ArrayList<>();
|
||||||
|
if (groupMappingMap != null && roleMap != null) {
|
||||||
|
updates.add(
|
||||||
|
createMetadataCASUpdate(prefix, oldGroupMappingMapValue, newGroupMappingMapValue, GROUP_MAPPINGS)
|
||||||
|
);
|
||||||
|
updates.add(
|
||||||
|
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean succeeded = connector.compareAndSwap(updates);
|
boolean succeeded = connector.compareAndSwap(updates);
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
if (userMap != null) {
|
cachedGroupMappingMaps.put(prefix, new BasicAuthorizerGroupMappingMapBundle(groupMappingMap, newGroupMappingMapValue));
|
||||||
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
|
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
|
||||||
}
|
|
||||||
if (roleMap != null) {
|
|
||||||
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
|
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
|
||||||
cacheNotifier.addUpdate(prefix, serializedUserAndRoleMap);
|
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private MetadataCASUpdate createMetadataCASUpdate(
|
||||||
|
String prefix,
|
||||||
|
byte[] oldValue,
|
||||||
|
byte[] newValue,
|
||||||
|
String columnName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new MetadataCASUpdate(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, columnName),
|
||||||
|
oldValue,
|
||||||
|
newValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createUser(String prefix, String userName)
|
public void createUser(String prefix, String userName)
|
||||||
{
|
{
|
||||||
|
@ -327,6 +478,21 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
deleteUserInternal(prefix, userName);
|
deleteUserInternal(prefix, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createGroupMapping(String prefix, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
createGroupMappingInternal(prefix, groupMapping);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteGroupMapping(String prefix, String groupMappingName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
deleteGroupMappingInternal(prefix, groupMappingName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createRole(String prefix, String roleName)
|
public void createRole(String prefix, String roleName)
|
||||||
{
|
{
|
||||||
|
@ -342,17 +508,31 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void assignRole(String prefix, String userName, String roleName)
|
public void assignUserRole(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
assignRoleInternal(prefix, userName, roleName);
|
assignUserRoleInternal(prefix, userName, roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unassignRole(String prefix, String userName, String roleName)
|
public void unassignUserRole(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
unassignRoleInternal(prefix, userName, roleName);
|
unassignUserRoleInternal(prefix, userName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignGroupMappingRole(String prefix, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
assignGroupMappingRoleInternal(prefix, groupMappingName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unassignGroupMappingRole(String prefix, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
|
||||||
|
unassignGroupMappingRoleInternal(prefix, groupMappingName, roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -370,6 +550,13 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
return userMapBundle == null ? null : userMapBundle.getUserMap();
|
return userMapBundle == null ? null : userMapBundle.getUserMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getCachedGroupMappingMap(String prefix)
|
||||||
|
{
|
||||||
|
BasicAuthorizerGroupMappingMapBundle groupMapBundle = cachedGroupMappingMaps.get(prefix);
|
||||||
|
return groupMapBundle == null ? null : groupMapBundle.getGroupMappingMap();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
|
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
|
||||||
|
@ -389,6 +576,17 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCurrentGroupMappingMapBytes(String prefix)
|
||||||
|
{
|
||||||
|
return connector.lookup(
|
||||||
|
connectorConfig.getConfigTable(),
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
|
||||||
|
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
|
||||||
|
getPrefixedKeyColumn(prefix, GROUP_MAPPINGS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getCurrentRoleMapBytes(String prefix)
|
public byte[] getCurrentRoleMapBytes(String prefix)
|
||||||
{
|
{
|
||||||
|
@ -407,7 +605,10 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
(authorizerName) -> {
|
(authorizerName) -> {
|
||||||
try {
|
try {
|
||||||
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(authorizerName);
|
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(authorizerName);
|
||||||
cacheNotifier.addUpdate(authorizerName, serializedUserAndRoleMap);
|
cacheNotifier.addUpdateUser(authorizerName, serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
byte[] serializeGroupAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(authorizerName);
|
||||||
|
cacheNotifier.addUpdateGroupMapping(authorizerName, serializeGroupAndRoleMap);
|
||||||
}
|
}
|
||||||
catch (IOException ioe) {
|
catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
throw new RuntimeException(ioe);
|
||||||
|
@ -429,6 +630,19 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
return objectMapper.writeValueAsBytes(userAndRoleMap);
|
return objectMapper.writeValueAsBytes(userAndRoleMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] getCurrentGroupMappingAndRoleMapSerialized(String prefix) throws IOException
|
||||||
|
{
|
||||||
|
BasicAuthorizerGroupMappingMapBundle groupMappingMapBundle = cachedGroupMappingMaps.get(prefix);
|
||||||
|
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
|
||||||
|
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = new GroupMappingAndRoleMap(
|
||||||
|
groupMappingMapBundle == null ? null : groupMappingMapBundle.getGroupMappingMap(),
|
||||||
|
roleMapBundle == null ? null : roleMapBundle.getRoleMap()
|
||||||
|
);
|
||||||
|
|
||||||
|
return objectMapper.writeValueAsBytes(groupMappingAndRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
private void createUserInternal(String prefix, String userName)
|
private void createUserInternal(String prefix, String userName)
|
||||||
{
|
{
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
|
@ -445,7 +659,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not create user[%s] due to concurrent update contention.", userName);
|
throw new ISE("Could not create user [%s] due to concurrent update contention.", userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteUserInternal(String prefix, String userName)
|
private void deleteUserInternal(String prefix, String userName)
|
||||||
|
@ -464,7 +678,45 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not delete user[%s] due to concurrent update contention.", userName);
|
throw new ISE("Could not delete user [%s] due to concurrent update contention.", userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGroupMappingInternal(String prefix, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (createGroupMappingOnce(prefix, groupMapping)) {
|
||||||
|
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 group mapping [%s] due to concurrent update contention.", groupMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteGroupMappingInternal(String prefix, String groupMappingName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (deleteGroupMappingOnce(prefix, groupMappingName)) {
|
||||||
|
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 group mapping [%s] due to concurrent update contention.", groupMappingName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRoleInternal(String prefix, String roleName)
|
private void createRoleInternal(String prefix, String roleName)
|
||||||
|
@ -483,7 +735,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not create role[%s] due to concurrent update contention.", roleName);
|
throw new ISE("Could not create role [%s] due to concurrent update contention.", roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteRoleInternal(String prefix, String roleName)
|
private void deleteRoleInternal(String prefix, String roleName)
|
||||||
|
@ -502,14 +754,14 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not delete role[%s] due to concurrent update contention.", roleName);
|
throw new ISE("Could not delete role [%s] due to concurrent update contention.", roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignRoleInternal(String prefix, String userName, String roleName)
|
private void assignUserRoleInternal(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (attempts < numRetries) {
|
while (attempts < numRetries) {
|
||||||
if (assignRoleOnce(prefix, userName, roleName)) {
|
if (assignUserRoleOnce(prefix, userName, roleName)) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
attempts++;
|
attempts++;
|
||||||
|
@ -521,14 +773,14 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not assign role[%s] to user[%s] due to concurrent update contention.", roleName, userName);
|
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)
|
private void unassignUserRoleInternal(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (attempts < numRetries) {
|
while (attempts < numRetries) {
|
||||||
if (unassignRoleOnce(prefix, userName, roleName)) {
|
if (unassignUserRoleOnce(prefix, userName, roleName)) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
attempts++;
|
attempts++;
|
||||||
|
@ -540,7 +792,50 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not unassign role[%s] from user[%s] due to concurrent update contention.", roleName, userName);
|
throw new ISE("Could not unassign role [%s] from user [%s] due to concurrent update contention.", roleName, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (assignGroupMappingRoleOnce(prefix, groupMappingName, 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 group mapping [%s] due to concurrent update contention.",
|
||||||
|
roleName,
|
||||||
|
groupMappingName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unassignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < numRetries) {
|
||||||
|
if (unassignGroupMappingRoleOnce(prefix, groupMappingName, 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 group mapping [%s] due to concurrent update contention.", roleName,
|
||||||
|
groupMappingName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)
|
private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)
|
||||||
|
@ -559,7 +854,20 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
throw new RuntimeException(ie);
|
throw new RuntimeException(ie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new ISE("Could not set permissions for role[%s] due to concurrent update contention.", roleName);
|
throw new ISE("Could not set permissions for role [%s] due to concurrent update contention.", roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createUserOnce(String prefix, String userName)
|
private boolean createUserOnce(String prefix, String userName)
|
||||||
|
@ -575,17 +883,30 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteUserOnce(String prefix, String userName)
|
private boolean deleteGroupMappingOnce(String prefix, String groupMappingName)
|
||||||
{
|
{
|
||||||
byte[] oldValue = getCurrentUserMapBytes(prefix);
|
byte[] oldValue = getCurrentGroupMappingMapBytes(prefix);
|
||||||
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(objectMapper, oldValue);
|
||||||
if (userMap.get(userName) == null) {
|
if (groupMappingMap.get(groupMappingName) == null) {
|
||||||
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
|
||||||
} else {
|
} else {
|
||||||
userMap.remove(userName);
|
groupMappingMap.remove(groupMappingName);
|
||||||
}
|
}
|
||||||
byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
byte[] newValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
|
||||||
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
|
return tryUpdateGroupMappingMap(prefix, groupMappingMap, oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createGroupMappingOnce(String prefix, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
byte[] oldValue = getCurrentGroupMappingMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(objectMapper, oldValue);
|
||||||
|
if (groupMappingMap.get(groupMapping.getName()) != null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] already exists.", groupMapping.getName());
|
||||||
|
} else {
|
||||||
|
groupMappingMap.put(groupMapping.getName(), groupMapping);
|
||||||
|
}
|
||||||
|
byte[] newValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
|
||||||
|
return tryUpdateGroupMappingMap(prefix, groupMappingMap, oldValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean createRoleOnce(String prefix, String roleName)
|
private boolean createRoleOnce(String prefix, String roleName)
|
||||||
|
@ -623,16 +944,31 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
user.getRoles().remove(roleName);
|
user.getRoles().remove(roleName);
|
||||||
}
|
}
|
||||||
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
|
||||||
|
|
||||||
|
byte[] oldGroupMapValue = getCurrentGroupMappingMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
oldGroupMapValue
|
||||||
|
);
|
||||||
|
for (BasicAuthorizerGroupMapping group : groupMap.values()) {
|
||||||
|
group.getRoles().remove(roleName);
|
||||||
|
}
|
||||||
|
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMap);
|
||||||
|
|
||||||
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
|
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
|
||||||
|
|
||||||
return tryUpdateUserAndRoleMap(
|
return tryUpdateUserAndRoleMap(
|
||||||
prefix,
|
prefix,
|
||||||
userMap, oldUserMapValue, newUserMapValue,
|
userMap, oldUserMapValue, newUserMapValue,
|
||||||
roleMap, oldRoleMapValue, newRoleMapValue
|
roleMap, oldRoleMapValue, newRoleMapValue
|
||||||
|
) && tryUpdateGroupMappingAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
groupMap, oldGroupMapValue, newGroupMapValue,
|
||||||
|
roleMap, newRoleMapValue, newRoleMapValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean assignRoleOnce(String prefix, String userName, String roleName)
|
private boolean assignUserRoleOnce(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
@ -668,7 +1004,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean unassignRoleOnce(String prefix, String userName, String roleName)
|
private boolean unassignUserRoleOnce(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
@ -704,6 +1040,78 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean assignGroupMappingRoleOnce(String prefix, String groupMappingName, 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[] oldGroupMappingMapValue = getCurrentGroupMappingMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
oldGroupMappingMapValue
|
||||||
|
);
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = groupMappingMap.get(groupMappingName);
|
||||||
|
if (groupMappingMap.get(groupMappingName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupMapping.getRoles().contains(roleName)) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] already has role [%s].", groupMappingName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupMapping.getRoles().add(roleName);
|
||||||
|
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
|
||||||
|
|
||||||
|
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
|
||||||
|
return tryUpdateGroupMappingAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
groupMappingMap, oldGroupMappingMapValue, newGroupMapValue,
|
||||||
|
roleMap, oldRoleMapValue, oldRoleMapValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean unassignGroupMappingRoleOnce(String prefix, String groupMappingName, 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[] oldGroupMappingMapValue = getCurrentGroupMappingMapBytes(prefix);
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
oldGroupMappingMapValue
|
||||||
|
);
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = groupMappingMap.get(groupMappingName);
|
||||||
|
if (groupMappingMap.get(groupMappingName) == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupMapping.getRoles().contains(roleName)) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not have role [%s].", groupMappingName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupMapping.getRoles().remove(roleName);
|
||||||
|
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
|
||||||
|
|
||||||
|
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
|
||||||
|
return tryUpdateGroupMappingAndRoleMap(
|
||||||
|
prefix,
|
||||||
|
groupMappingMap, oldGroupMappingMapValue, newGroupMapValue,
|
||||||
|
roleMap, oldRoleMapValue, oldRoleMapValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean setPermissionsOnce(String prefix, String roleName, List<ResourceAction> permissions)
|
private boolean setPermissionsOnce(String prefix, String roleName, List<ResourceAction> permissions)
|
||||||
{
|
{
|
||||||
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
|
||||||
|
@ -723,10 +1131,14 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
return tryUpdateRoleMap(prefix, roleMap, oldRoleMapValue, newRoleMapValue);
|
return tryUpdateRoleMap(prefix, roleMap, oldRoleMapValue, newRoleMapValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSuperusers(
|
private void initSuperUsersAndGroupMapping(
|
||||||
String authorizerName,
|
String authorizerName,
|
||||||
Map<String, BasicAuthorizerUser> userMap,
|
Map<String, BasicAuthorizerUser> userMap,
|
||||||
Map<String, BasicAuthorizerRole> roleMap
|
Map<String, BasicAuthorizerRole> roleMap,
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
|
||||||
|
String initialAdminUser,
|
||||||
|
String initialAdminRole,
|
||||||
|
String initialAdminGroupMapping
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!roleMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
if (!roleMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
||||||
|
@ -739,14 +1151,38 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAu
|
||||||
setPermissionsInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, SUPERUSER_PERMISSIONS);
|
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)) {
|
if (!userMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
|
||||||
createUserInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
|
createUserInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
|
||||||
assignRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.ADMIN_NAME);
|
assignUserRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.ADMIN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
|
||||||
|
createUserInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
assignUserRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, BasicAuthUtils.INTERNAL_USER_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialAdminRole != null
|
||||||
|
&& !(initialAdminRole.equals(BasicAuthUtils.ADMIN_NAME) || initialAdminRole.equals(BasicAuthUtils.INTERNAL_USER_NAME))
|
||||||
|
&& !roleMap.containsKey(initialAdminRole)) {
|
||||||
|
createRoleInternal(authorizerName, initialAdminRole);
|
||||||
|
setPermissionsInternal(authorizerName, initialAdminRole, SUPERUSER_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialAdminUser != null
|
||||||
|
&& !(initialAdminUser.equals(BasicAuthUtils.ADMIN_NAME) || initialAdminUser.equals(BasicAuthUtils.INTERNAL_USER_NAME))
|
||||||
|
&& !userMap.containsKey(initialAdminUser)) {
|
||||||
|
createUserInternal(authorizerName, initialAdminUser);
|
||||||
|
assignUserRoleInternal(authorizerName, initialAdminUser, initialAdminRole == null ? BasicAuthUtils.ADMIN_NAME : initialAdminRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialAdminGroupMapping != null && !groupMappingMap.containsKey(BasicAuthUtils.ADMIN_GROUP_MAPPING_NAME)) {
|
||||||
|
BasicAuthorizerGroupMapping groupMapping =
|
||||||
|
new BasicAuthorizerGroupMapping(
|
||||||
|
BasicAuthUtils.ADMIN_GROUP_MAPPING_NAME,
|
||||||
|
initialAdminGroupMapping,
|
||||||
|
new HashSet<>(Collections.singletonList(initialAdminRole == null ? BasicAuthUtils.ADMIN_NAME : initialAdminRole))
|
||||||
|
);
|
||||||
|
createGroupMappingInternal(authorizerName, groupMapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.druid.security.basic.authorization.db.updater;
|
package org.apache.druid.security.basic.authorization.db.updater;
|
||||||
|
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
import org.apache.druid.server.security.ResourceAction;
|
import org.apache.druid.server.security.ResourceAction;
|
||||||
|
@ -43,6 +44,16 @@ public class NoopBasicAuthorizerMetadataStorageUpdater implements BasicAuthorize
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createGroupMapping(String prefix, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteGroupMapping(String prefix, String groupMappingName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createRole(String prefix, String roleName)
|
public void createRole(String prefix, String roleName)
|
||||||
{
|
{
|
||||||
|
@ -54,12 +65,22 @@ public class NoopBasicAuthorizerMetadataStorageUpdater implements BasicAuthorize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void assignRole(String prefix, String userName, String roleName)
|
public void assignUserRole(String prefix, String userName, String roleName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unassignRole(String prefix, String userName, String roleName)
|
public void unassignUserRole(String prefix, String userName, String roleName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignGroupMappingRole(String prefix, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unassignGroupMappingRole(String prefix, String groupMappingName, String roleName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +95,12 @@ public class NoopBasicAuthorizerMetadataStorageUpdater implements BasicAuthorize
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getCachedGroupMappingMap(String prefix)
|
||||||
|
{
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
|
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
|
||||||
{
|
{
|
||||||
|
@ -92,6 +119,12 @@ public class NoopBasicAuthorizerMetadataStorageUpdater implements BasicAuthorize
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCurrentGroupMappingMapBytes(String prefix)
|
||||||
|
{
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refreshAllNotification()
|
public void refreshAllNotification()
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.google.inject.Inject;
|
||||||
import com.sun.jersey.spi.container.ResourceFilters;
|
import com.sun.jersey.spi.container.ResourceFilters;
|
||||||
import org.apache.druid.guice.LazySingleton;
|
import org.apache.druid.guice.LazySingleton;
|
||||||
import org.apache.druid.security.basic.BasicSecurityResourceFilter;
|
import org.apache.druid.security.basic.BasicSecurityResourceFilter;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.server.security.ResourceAction;
|
import org.apache.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -44,7 +45,7 @@ import java.util.List;
|
||||||
@LazySingleton
|
@LazySingleton
|
||||||
public class BasicAuthorizerResource
|
public class BasicAuthorizerResource
|
||||||
{
|
{
|
||||||
private BasicAuthorizerResourceHandler resourceHandler;
|
private final BasicAuthorizerResourceHandler resourceHandler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BasicAuthorizerResource(
|
public BasicAuthorizerResource(
|
||||||
|
@ -108,6 +109,24 @@ public class BasicAuthorizerResource
|
||||||
return resourceHandler.getAllUsers(authorizerName);
|
return resourceHandler.getAllUsers(authorizerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return List of all groupMappings
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/groupMappings")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getAllGroupMappings(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getAllGroupMappings(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param req HTTP request
|
* @param req HTTP request
|
||||||
* @param userName Name of user to retrieve information about
|
* @param userName Name of user to retrieve information about
|
||||||
|
@ -130,6 +149,27 @@ public class BasicAuthorizerResource
|
||||||
return resourceHandler.getUser(authorizerName, userName, full != null, simplifyPermissions != null);
|
return resourceHandler.getUser(authorizerName, userName, full != null, simplifyPermissions != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param groupMappingName Name of groupMapping to retrieve information about
|
||||||
|
*
|
||||||
|
* @return Name, groupPattern, roles, and permissions of the groupMapping with groupMappingName, 400 error response if groupMapping doesn't exist
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/groupMappings/{groupMappingName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getGroupMapping(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("groupMappingName") final String groupMappingName,
|
||||||
|
@QueryParam("full") String full
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getGroupMapping(authorizerName, groupMappingName, full != null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new user with name userName
|
* Create a new user with name userName
|
||||||
*
|
*
|
||||||
|
@ -174,6 +214,54 @@ public class BasicAuthorizerResource
|
||||||
return resourceHandler.deleteUser(authorizerName, userName);
|
return resourceHandler.deleteUser(authorizerName, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new groupMapping with name groupMappingName
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param groupMappingName Name to assign the new groupMapping
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if groupMapping already exists
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/groupMappings/{groupMappingName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response createGroupMapping(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("groupMappingName") String groupMappingName,
|
||||||
|
BasicAuthorizerGroupMapping groupMapping
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.createGroupMapping(
|
||||||
|
authorizerName,
|
||||||
|
new BasicAuthorizerGroupMapping(groupMappingName, groupMapping.getGroupPattern(), groupMapping.getRoles())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a groupMapping with name groupMappingName
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param groupMappingName Name of groupMapping to delete
|
||||||
|
*
|
||||||
|
* @return OK response, or 400 error response if groupMapping doesn't exist
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authorizerName}/groupMappings/{groupMappingName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response deleteGroupMapping(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("groupMappingName") String groupMappingName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.deleteGroupMapping(authorizerName, groupMappingName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param req HTTP request
|
* @param req HTTP request
|
||||||
*
|
*
|
||||||
|
@ -198,7 +286,7 @@ public class BasicAuthorizerResource
|
||||||
* @param req HTTP request
|
* @param req HTTP request
|
||||||
* @param roleName Name of role
|
* @param roleName Name of role
|
||||||
*
|
*
|
||||||
* @return Role name, users with role, and permissions of role. 400 error if role doesn't exist.
|
* @return Role name, users with role, groupMappings with role, and permissions of role. 400 error if role doesn't exist.
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/db/{authorizerName}/roles/{roleName}")
|
@Path("/db/{authorizerName}/roles/{roleName}")
|
||||||
|
@ -308,6 +396,54 @@ public class BasicAuthorizerResource
|
||||||
return resourceHandler.unassignRoleFromUser(authorizerName, userName, roleName);
|
return resourceHandler.unassignRoleFromUser(authorizerName, userName, roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a role to a groupMapping.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param groupMappingName Name of groupMapping
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if groupMapping/role don't exist, or if groupMapping already has the role
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/db/{authorizerName}/groupMappings/{groupMappingName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response assignRoleToGroupMapping(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("groupMappingName") String groupMappingName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.assignRoleToGroupMapping(authorizerName, groupMappingName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a role from a groupMapping.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param groupMappingName Name of groupMapping
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if groupMapping/role don't exist, or if groupMapping does not have the role.
|
||||||
|
*/
|
||||||
|
@DELETE
|
||||||
|
@Path("/db/{authorizerName}/groupMappings/{groupMappingName}/roles/{roleName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response unassignRoleFromGroupMapping(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("groupMappingName") String groupMappingName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.unassignRoleFromGroupMapping(authorizerName, groupMappingName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the permissions of a role. This replaces the previous permissions of the role.
|
* Set the permissions of a role. This replaces the previous permissions of the role.
|
||||||
*
|
*
|
||||||
|
@ -332,6 +468,28 @@ public class BasicAuthorizerResource
|
||||||
return resourceHandler.setRolePermissions(authorizerName, roleName, permissions);
|
return resourceHandler.setRolePermissions(authorizerName, roleName, permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the permissions of a role.
|
||||||
|
*
|
||||||
|
* @param req HTTP request
|
||||||
|
* @param roleName Name of role
|
||||||
|
*
|
||||||
|
* @return OK response. 400 error if role doesn't exist.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/roles/{roleName}/permissions")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getRolePermissions(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
@PathParam("roleName") String roleName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getRolePermissions(authorizerName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param req HTTP request
|
* @param req HTTP request
|
||||||
*
|
*
|
||||||
|
@ -347,24 +505,79 @@ public class BasicAuthorizerResource
|
||||||
@PathParam("authorizerName") final String authorizerName
|
@PathParam("authorizerName") final String authorizerName
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return resourceHandler.getCachedMaps(authorizerName);
|
return resourceHandler.getCachedUserMaps(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param req HTTP request
|
||||||
|
*
|
||||||
|
* @return serialized groupMapping map
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/db/{authorizerName}/cachedSerializedGroupMappingMap")
|
||||||
|
@Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response getCachedSerializedGroupMap(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.getCachedGroupMappingMaps(authorizerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for update notifications for the auth storage
|
* Listen for update notifications for the user auth storage
|
||||||
|
* @deprecated path /listen/{authorizerName} is to replaced by /listen/users/{authorizerName}
|
||||||
|
* use {@link #authorizerUserUpdateListener(HttpServletRequest, String, byte[])} instead
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("/listen/{authorizerName}")
|
@Path("/listen/{authorizerName}")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@ResourceFilters(BasicSecurityResourceFilter.class)
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
@Deprecated
|
||||||
public Response authorizerUpdateListener(
|
public Response authorizerUpdateListener(
|
||||||
@Context HttpServletRequest req,
|
@Context HttpServletRequest req,
|
||||||
@PathParam("authorizerName") final String authorizerName,
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
byte[] serializedUserAndRoleMap
|
byte[] serializedUserAndRoleMap
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return resourceHandler.authorizerUpdateListener(authorizerName, serializedUserAndRoleMap);
|
return resourceHandler.authorizerUserUpdateListener(authorizerName, serializedUserAndRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for update notifications for the user auth storage
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/listen/users/{authorizerName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response authorizerUserUpdateListener(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
byte[] serializedUserAndRoleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.authorizerUserUpdateListener(authorizerName, serializedUserAndRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for update notifications for the groupMapping auth storage
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/listen/groupMappings/{authorizerName}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@ResourceFilters(BasicSecurityResourceFilter.class)
|
||||||
|
public Response authorizerGroupMappingUpdateListener(
|
||||||
|
@Context HttpServletRequest req,
|
||||||
|
@PathParam("authorizerName") final String authorizerName,
|
||||||
|
byte[] serializedGroupMappingAndRoleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return resourceHandler.authorizerGroupMappingUpdateListener(authorizerName, serializedGroupMappingAndRoleMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.druid.security.basic.authorization.endpoint;
|
package org.apache.druid.security.basic.authorization.endpoint;
|
||||||
|
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.server.security.ResourceAction;
|
import org.apache.druid.server.security.ResourceAction;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -34,12 +35,20 @@ public interface BasicAuthorizerResourceHandler
|
||||||
// coordinator methods
|
// coordinator methods
|
||||||
Response getAllUsers(String authorizerName);
|
Response getAllUsers(String authorizerName);
|
||||||
|
|
||||||
|
Response getAllGroupMappings(String authorizerName);
|
||||||
|
|
||||||
Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions);
|
Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions);
|
||||||
|
|
||||||
|
Response getGroupMapping(String authorizerName, String groupMappingName, boolean isFull);
|
||||||
|
|
||||||
Response createUser(String authorizerName, String userName);
|
Response createUser(String authorizerName, String userName);
|
||||||
|
|
||||||
|
Response createGroupMapping(String authorizerName, BasicAuthorizerGroupMapping groupMapping);
|
||||||
|
|
||||||
Response deleteUser(String authorizerName, String userName);
|
Response deleteUser(String authorizerName, String userName);
|
||||||
|
|
||||||
|
Response deleteGroupMapping(String authorizerName, String groupMappingName);
|
||||||
|
|
||||||
Response getAllRoles(String authorizerName);
|
Response getAllRoles(String authorizerName);
|
||||||
|
|
||||||
Response getRole(String authorizerName, String roleName, boolean isFull, boolean simplifyPermissions);
|
Response getRole(String authorizerName, String roleName, boolean isFull, boolean simplifyPermissions);
|
||||||
|
@ -50,16 +59,26 @@ public interface BasicAuthorizerResourceHandler
|
||||||
|
|
||||||
Response assignRoleToUser(String authorizerName, String userName, String roleName);
|
Response assignRoleToUser(String authorizerName, String userName, String roleName);
|
||||||
|
|
||||||
|
Response assignRoleToGroupMapping(String authorizerName, String groupMappingName, String roleName);
|
||||||
|
|
||||||
Response unassignRoleFromUser(String authorizerName, String userName, String roleName);
|
Response unassignRoleFromUser(String authorizerName, String userName, String roleName);
|
||||||
|
|
||||||
|
Response unassignRoleFromGroupMapping(String authorizerName, String groupMappingName, String roleName);
|
||||||
|
|
||||||
Response setRolePermissions(String authorizerName, String roleName, List<ResourceAction> permissions);
|
Response setRolePermissions(String authorizerName, String roleName, List<ResourceAction> permissions);
|
||||||
|
|
||||||
Response getCachedMaps(String authorizerName);
|
Response getRolePermissions(String authorizerName, String roleName);
|
||||||
|
|
||||||
|
Response getCachedUserMaps(String authorizerName);
|
||||||
|
|
||||||
|
Response getCachedGroupMappingMaps(String authorizerName);
|
||||||
|
|
||||||
Response refreshAll();
|
Response refreshAll();
|
||||||
|
|
||||||
// non-coordinator methods
|
// non-coordinator methods
|
||||||
Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap);
|
Response authorizerUserUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap);
|
||||||
|
|
||||||
|
Response authorizerGroupMappingUpdateListener(String authorizerName, byte[] serializedGroupMappingAndRoleMap);
|
||||||
|
|
||||||
// common
|
// common
|
||||||
Response getLoadStatus();
|
Response getLoadStatus();
|
||||||
|
|
|
@ -29,12 +29,15 @@ import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
import org.apache.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
import org.apache.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMappingFull;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleSimplifiedPermissions;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleSimplifiedPermissions;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFull;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFull;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFullSimplifiedPermissions;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFullSimplifiedPermissions;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
|
||||||
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
|
||||||
import org.apache.druid.server.security.Authorizer;
|
import org.apache.druid.server.security.Authorizer;
|
||||||
import org.apache.druid.server.security.AuthorizerMapper;
|
import org.apache.druid.server.security.AuthorizerMapper;
|
||||||
|
@ -93,6 +96,21 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
return Response.ok(userMap.keySet()).build();
|
return Response.ok(userMap.keySet()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllGroupMappings(String authorizerName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentGroupMappingMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
return Response.ok(groupMappingMap.keySet()).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions)
|
public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions)
|
||||||
{
|
{
|
||||||
|
@ -108,6 +126,21 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getGroupMapping(String authorizerName, String groupMappingName, boolean isFull)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFull) {
|
||||||
|
return getGroupMappingFull(authorizerName, groupMappingName);
|
||||||
|
} else {
|
||||||
|
return getGroupMappingSimple(authorizerName, groupMappingName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response createUser(String authorizerName, String userName)
|
public Response createUser(String authorizerName, String userName)
|
||||||
{
|
{
|
||||||
|
@ -125,6 +158,23 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createGroupMapping(String authorizerName, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.createGroupMapping(authorizerName, groupMapping);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response deleteUser(String authorizerName, String userName)
|
public Response deleteUser(String authorizerName, String userName)
|
||||||
{
|
{
|
||||||
|
@ -142,6 +192,23 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteGroupMapping(String authorizerName, String groupMappingName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.deleteGroupMapping(authorizerName, groupMappingName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getAllRoles(String authorizerName)
|
public Response getAllRoles(String authorizerName)
|
||||||
{
|
{
|
||||||
|
@ -216,7 +283,24 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
storageUpdater.assignRole(authorizerName, userName, roleName);
|
storageUpdater.assignUserRole(authorizerName, userName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response assignRoleToGroupMapping(String authorizerName, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.assignGroupMappingRole(authorizerName, groupMappingName, roleName);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
catch (BasicSecurityDBResourceException cfe) {
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
@ -233,7 +317,24 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
storageUpdater.unassignRole(authorizerName, userName, roleName);
|
storageUpdater.unassignUserRole(authorizerName, userName, roleName);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(cfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response unassignRoleFromGroupMapping(String authorizerName, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageUpdater.unassignGroupMappingRole(authorizerName, groupMappingName, roleName);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
catch (BasicSecurityDBResourceException cfe) {
|
catch (BasicSecurityDBResourceException cfe) {
|
||||||
|
@ -259,7 +360,18 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getCachedMaps(String authorizerName)
|
public Response getRolePermissions(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPermissions(authorizerName, roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedUserMaps(String authorizerName)
|
||||||
{
|
{
|
||||||
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
if (authorizer == null) {
|
if (authorizer == null) {
|
||||||
|
@ -274,6 +386,21 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
return Response.ok(userAndRoleMap).build();
|
return Response.ok(userAndRoleMap).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedGroupMappingMaps(String authorizerName)
|
||||||
|
{
|
||||||
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
|
if (authorizer == null) {
|
||||||
|
return makeResponseForAuthorizerNotFound(authorizerName);
|
||||||
|
}
|
||||||
|
GroupMappingAndRoleMap groupMappingAndRoleMap = new GroupMappingAndRoleMap(
|
||||||
|
storageUpdater.getCachedGroupMappingMap(authorizerName),
|
||||||
|
storageUpdater.getCachedRoleMap(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Response.ok(groupMappingAndRoleMap).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response refreshAll()
|
public Response refreshAll()
|
||||||
{
|
{
|
||||||
|
@ -282,7 +409,13 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
public Response authorizerUserUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
||||||
|
{
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authorizerGroupMappingUpdateListener(String authorizerName, byte[] serializedGroupMappingAndRoleMap)
|
||||||
{
|
{
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
|
@ -293,7 +426,9 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
Map<String, Boolean> loadStatus = new HashMap<>();
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
authorizerMap.forEach(
|
authorizerMap.forEach(
|
||||||
(authorizerName, authorizer) -> {
|
(authorizerName, authorizer) -> {
|
||||||
loadStatus.put(authorizerName, storageUpdater.getCachedUserMap(authorizerName) != null);
|
loadStatus.put(authorizerName, storageUpdater.getCachedUserMap(authorizerName) != null &&
|
||||||
|
storageUpdater.getCachedGroupMappingMap(authorizerName) != null &&
|
||||||
|
storageUpdater.getCachedRoleMap(authorizerName) != null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return Response.ok(loadStatus).build();
|
return Response.ok(loadStatus).build();
|
||||||
|
@ -344,17 +479,17 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
|
||||||
objectMapper,
|
|
||||||
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BasicAuthorizerUser user = userMap.get(userName);
|
BasicAuthorizerUser user = userMap.get(userName);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
if (simplifyPermissions) {
|
if (simplifyPermissions) {
|
||||||
Set<BasicAuthorizerRoleSimplifiedPermissions> roles = getRolesForUserWithSimplifiedPermissions(user, roleMap);
|
Set<BasicAuthorizerRoleSimplifiedPermissions> roles = getRolesForUserWithSimplifiedPermissions(user, roleMap);
|
||||||
BasicAuthorizerUserFullSimplifiedPermissions fullUser = new BasicAuthorizerUserFullSimplifiedPermissions(
|
BasicAuthorizerUserFullSimplifiedPermissions fullUser = new BasicAuthorizerUserFullSimplifiedPermissions(
|
||||||
|
@ -412,6 +547,60 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response getGroupMappingSimple(String authorizerName, String groupMappingName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappings = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentGroupMappingMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = groupMappings.get(groupMappingName);
|
||||||
|
if (groupMapping == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
|
||||||
|
}
|
||||||
|
return Response.ok(groupMapping).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response getGroupMappingFull(String authorizerName, String groupMappingName)
|
||||||
|
{
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappings = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentGroupMappingMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = groupMappings.get(groupMappingName);
|
||||||
|
if (groupMapping == null) {
|
||||||
|
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<BasicAuthorizerRole> roles = new HashSet<>();
|
||||||
|
for (String roleName : groupMapping.getRoles()) {
|
||||||
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
|
if (role == null) {
|
||||||
|
log.error("Group mapping [%s] had role [%s], but role was not found.", groupMappingName, roleName);
|
||||||
|
} else {
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMappingFull fullGroup = new BasicAuthorizerGroupMappingFull(groupMapping.getName(), groupMapping.getGroupPattern(), roles);
|
||||||
|
return Response.ok(fullGroup).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Response getRoleSimple(String authorizerName, String roleName, boolean simplifyPermissions)
|
private Response getRoleSimple(String authorizerName, String roleName, boolean simplifyPermissions)
|
||||||
{
|
{
|
||||||
|
@ -444,29 +633,42 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
storageUpdater.getCurrentRoleMapBytes(authorizerName)
|
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 {
|
try {
|
||||||
BasicAuthorizerRole role = roleMap.get(roleName);
|
BasicAuthorizerRole role = roleMap.get(roleName);
|
||||||
if (role == null) {
|
if (role == null) {
|
||||||
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentUserMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
storageUpdater.getCurrentGroupMappingMapBytes(authorizerName)
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> users = new HashSet<>();
|
||||||
|
for (BasicAuthorizerUser user : userMap.values()) {
|
||||||
|
if (user.getRoles().contains(roleName)) {
|
||||||
|
users.add(user.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> groupMappings = new HashSet<>();
|
||||||
|
for (BasicAuthorizerGroupMapping group : groupMappingMap.values()) {
|
||||||
|
if (group.getRoles().contains(roleName)) {
|
||||||
|
groupMappings.add(group.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (simplifyPermissions) {
|
if (simplifyPermissions) {
|
||||||
return Response.ok(new BasicAuthorizerRoleSimplifiedPermissions(role, users)).build();
|
return Response.ok(new BasicAuthorizerRoleSimplifiedPermissions(role, users)).build();
|
||||||
} else {
|
} else {
|
||||||
BasicAuthorizerRoleFull roleFull = new BasicAuthorizerRoleFull(
|
BasicAuthorizerRoleFull roleFull = new BasicAuthorizerRoleFull(
|
||||||
roleName,
|
roleName,
|
||||||
users,
|
users,
|
||||||
|
groupMappings,
|
||||||
role.getPermissions()
|
role.getPermissions()
|
||||||
);
|
);
|
||||||
return Response.ok(roleFull).build();
|
return Response.ok(roleFull).build();
|
||||||
|
@ -476,4 +678,23 @@ public class CoordinatorBasicAuthorizerResourceHandler implements BasicAuthorize
|
||||||
return makeResponseForBasicSecurityDBResourceException(e);
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response getPermissions(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.getPermissions()).build();
|
||||||
|
}
|
||||||
|
catch (BasicSecurityDBResourceException e) {
|
||||||
|
return makeResponseForBasicSecurityDBResourceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.apache.druid.java.util.common.StringUtils;
|
||||||
import org.apache.druid.java.util.common.logger.Logger;
|
import org.apache.druid.java.util.common.logger.Logger;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheManager;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.server.security.Authorizer;
|
import org.apache.druid.server.security.Authorizer;
|
||||||
import org.apache.druid.server.security.AuthorizerMapper;
|
import org.apache.druid.server.security.AuthorizerMapper;
|
||||||
import org.apache.druid.server.security.ResourceAction;
|
import org.apache.druid.server.security.ResourceAction;
|
||||||
|
@ -69,24 +70,49 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getAllGroupMappings(String authorizerName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions)
|
public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions)
|
||||||
{
|
{
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getGroupMapping(String authorizerName, String groupMappingName, boolean isFull)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response createUser(String authorizerName, String userName)
|
public Response createUser(String authorizerName, String userName)
|
||||||
{
|
{
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createGroupMapping(String authorizerName, BasicAuthorizerGroupMapping groupMapping)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response deleteUser(String authorizerName, String userName)
|
public Response deleteUser(String authorizerName, String userName)
|
||||||
{
|
{
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response deleteGroupMapping(String authorizerName, String groupMappingName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getAllRoles(String authorizerName)
|
public Response getAllRoles(String authorizerName)
|
||||||
{
|
{
|
||||||
|
@ -117,12 +143,24 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response assignRoleToGroupMapping(String authorizerName, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response unassignRoleFromUser(String authorizerName, String userName, String roleName)
|
public Response unassignRoleFromUser(String authorizerName, String userName, String roleName)
|
||||||
{
|
{
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response unassignRoleFromGroupMapping(String authorizerName, String groupMappingName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response setRolePermissions(String authorizerName, String roleName, List<ResourceAction> permissions)
|
public Response setRolePermissions(String authorizerName, String roleName, List<ResourceAction> permissions)
|
||||||
{
|
{
|
||||||
|
@ -130,7 +168,19 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getCachedMaps(String authorizerName)
|
public Response getRolePermissions(String authorizerName, String roleName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedUserMaps(String authorizerName)
|
||||||
|
{
|
||||||
|
return NOT_FOUND_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response getCachedGroupMappingMaps(String authorizerName)
|
||||||
{
|
{
|
||||||
return NOT_FOUND_RESPONSE;
|
return NOT_FOUND_RESPONSE;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +192,7 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response authorizerUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
public Response authorizerUserUpdateListener(String authorizerName, byte[] serializedUserAndRoleMap)
|
||||||
{
|
{
|
||||||
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName);
|
||||||
if (authorizer == null) {
|
if (authorizer == null) {
|
||||||
|
@ -156,7 +206,26 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheManager.handleAuthorizerUpdate(authorizerName, serializedUserAndRoleMap);
|
cacheManager.handleAuthorizerUserUpdate(authorizerName, serializedUserAndRoleMap);
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response authorizerGroupMappingUpdateListener(String authorizerName, byte[] serializedGroupMappingAndRoleMap)
|
||||||
|
{
|
||||||
|
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.handleAuthorizerGroupMappingUpdate(authorizerName, serializedGroupMappingAndRoleMap);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +235,10 @@ public class DefaultBasicAuthorizerResourceHandler implements BasicAuthorizerRes
|
||||||
Map<String, Boolean> loadStatus = new HashMap<>();
|
Map<String, Boolean> loadStatus = new HashMap<>();
|
||||||
authorizerMap.forEach(
|
authorizerMap.forEach(
|
||||||
(authorizerName, authorizer) -> {
|
(authorizerName, authorizer) -> {
|
||||||
loadStatus.put(authorizerName, cacheManager.getUserMap(authorizerName) != null);
|
loadStatus.put(authorizerName, cacheManager.getUserMap(authorizerName) != null &&
|
||||||
|
cacheManager.getRoleMap(authorizerName) != null &&
|
||||||
|
cacheManager.getGroupMappingMap(authorizerName) != null &&
|
||||||
|
cacheManager.getGroupMappingRoleMap(authorizerName) != null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return Response.ok(loadStatus).build();
|
return Response.ok(loadStatus).build();
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BasicAuthorizerGroupMapping
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final String groupPattern;
|
||||||
|
private final Set<String> roles;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerGroupMapping(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("groupPattern") String groupPattern,
|
||||||
|
@JsonProperty("roles") Set<String> roles
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.groupPattern = groupPattern;
|
||||||
|
this.roles = roles == null ? new HashSet<>() : roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getGroupPattern()
|
||||||
|
{
|
||||||
|
return groupPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMapping that = (BasicAuthorizerGroupMapping) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getGroupPattern() != null ? !getGroupPattern().equals(that.getGroupPattern()) : that.getGroupPattern() != 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
|
||||||
|
+ (getGroupPattern() != null ? getGroupPattern().hashCode() : 0)
|
||||||
|
+ (getRoles() != null ? getRoles().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BasicAuthorizerGroupMappingFull
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final String groupPattern;
|
||||||
|
private final Set<BasicAuthorizerRole> roles;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerGroupMappingFull(
|
||||||
|
@JsonProperty("name") String name,
|
||||||
|
@JsonProperty("groupPattern") String groupPattern,
|
||||||
|
@JsonProperty("roles") Set<BasicAuthorizerRole> roles
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.groupPattern = groupPattern;
|
||||||
|
this.roles = roles == null ? new HashSet<>() : roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getGroupPattern()
|
||||||
|
{
|
||||||
|
return groupPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMappingFull that = (BasicAuthorizerGroupMappingFull) o;
|
||||||
|
|
||||||
|
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getGroupPattern() != null ? !getGroupPattern().equals(that.getGroupPattern()) : that.getGroupPattern() != 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
|
||||||
|
+ (getGroupPattern() != null ? getGroupPattern().hashCode() : 0)
|
||||||
|
+ (getRoles() != null ? getRoles().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BasicAuthorizerGroupMappingMapBundle
|
||||||
|
{
|
||||||
|
private final Map<String, BasicAuthorizerGroupMapping> groupMappingMap;
|
||||||
|
private final byte[] serializedGroupMappingMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public BasicAuthorizerGroupMappingMapBundle(
|
||||||
|
@JsonProperty("groupMappingMap") Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
|
||||||
|
@JsonProperty("serializedGroupMappingMap") byte[] serializedGroupMappingMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.groupMappingMap = groupMappingMap;
|
||||||
|
this.serializedGroupMappingMap = serializedGroupMappingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getGroupMappingMap()
|
||||||
|
{
|
||||||
|
return groupMappingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public byte[] getSerializedGroupMappingMap()
|
||||||
|
{
|
||||||
|
return serializedGroupMappingMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,7 @@ public class BasicAuthorizerPermission
|
||||||
this.resourceNamePattern = resourceNamePattern;
|
this.resourceNamePattern = resourceNamePattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasicAuthorizerPermission(
|
private BasicAuthorizerPermission(
|
||||||
ResourceAction resourceAction
|
ResourceAction resourceAction
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,17 +34,20 @@ public class BasicAuthorizerRoleFull
|
||||||
{
|
{
|
||||||
private final String name;
|
private final String name;
|
||||||
private final Set<String> users;
|
private final Set<String> users;
|
||||||
|
private final Set<String> groups;
|
||||||
private final List<BasicAuthorizerPermission> permissions;
|
private final List<BasicAuthorizerPermission> permissions;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public BasicAuthorizerRoleFull(
|
public BasicAuthorizerRoleFull(
|
||||||
@JsonProperty("name") String name,
|
@JsonProperty("name") String name,
|
||||||
@JsonProperty("users") Set<String> users,
|
@JsonProperty("users") Set<String> users,
|
||||||
|
@JsonProperty("groups") Set<String> groups,
|
||||||
@JsonProperty("permissions") List<BasicAuthorizerPermission> permissions
|
@JsonProperty("permissions") List<BasicAuthorizerPermission> permissions
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.users = users;
|
this.users = users;
|
||||||
|
this.groups = groups;
|
||||||
this.permissions = permissions == null ? new ArrayList<>() : permissions;
|
this.permissions = permissions == null ? new ArrayList<>() : permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +69,12 @@ public class BasicAuthorizerRoleFull
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Set<String> getGroups()
|
||||||
|
{
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o)
|
public boolean equals(Object o)
|
||||||
{
|
{
|
||||||
|
@ -84,6 +93,9 @@ public class BasicAuthorizerRoleFull
|
||||||
if (getUsers() != null ? !getUsers().equals(that.getUsers()) : that.getUsers() != null) {
|
if (getUsers() != null ? !getUsers().equals(that.getUsers()) : that.getUsers() != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (getGroups() != null ? !getGroups().equals(that.getGroups()) : that.getGroups() != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return getPermissions() != null ? getPermissions().equals(that.getPermissions()) : that.getPermissions() == null;
|
return getPermissions() != null ? getPermissions().equals(that.getPermissions()) : that.getPermissions() == null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,6 +105,7 @@ public class BasicAuthorizerRoleFull
|
||||||
{
|
{
|
||||||
int result = getName() != null ? getName().hashCode() : 0;
|
int result = getName() != null ? getName().hashCode() : 0;
|
||||||
result = 31 * result + (getUsers() != null ? getUsers().hashCode() : 0);
|
result = 31 * result + (getUsers() != null ? getUsers().hashCode() : 0);
|
||||||
|
result = 31 * result + (getGroups() != null ? getGroups().hashCode() : 0);
|
||||||
result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0);
|
result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.basic.authorization.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class GroupMappingAndRoleMap
|
||||||
|
{
|
||||||
|
@JsonProperty
|
||||||
|
private Map<String, BasicAuthorizerGroupMapping> groupMappingMap;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private Map<String, BasicAuthorizerRole> roleMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public GroupMappingAndRoleMap(
|
||||||
|
@JsonProperty("groupMappingMap") Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
|
||||||
|
@JsonProperty("roleMap") Map<String, BasicAuthorizerRole> roleMap
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.groupMappingMap = groupMappingMap;
|
||||||
|
this.roleMap = roleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerGroupMapping> getGroupMappingMap()
|
||||||
|
{
|
||||||
|
return groupMappingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public Map<String, BasicAuthorizerRole> getRoleMap()
|
||||||
|
{
|
||||||
|
return roleMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,11 +24,13 @@ import com.google.inject.Provider;
|
||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
import org.apache.druid.java.util.common.StringUtils;
|
import org.apache.druid.java.util.common.StringUtils;
|
||||||
import org.apache.druid.metadata.DefaultPasswordProvider;
|
import org.apache.druid.metadata.DefaultPasswordProvider;
|
||||||
|
import org.apache.druid.security.basic.BasicSecurityAuthenticationException;
|
||||||
import org.apache.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
import org.apache.druid.security.basic.authentication.BasicHTTPAuthenticator;
|
||||||
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import org.apache.druid.security.basic.authentication.validator.CredentialsValidator;
|
||||||
import org.apache.druid.server.security.AuthConfig;
|
import org.apache.druid.server.security.AuthConfig;
|
||||||
import org.apache.druid.server.security.AuthenticationResult;
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
|
@ -52,7 +54,7 @@ public class BasicHTTPAuthenticatorTest
|
||||||
new BasicAuthenticatorCacheManager()
|
new BasicAuthenticatorCacheManager()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void handleAuthenticatorUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
public void handleAuthenticatorUserMapUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -74,10 +76,12 @@ public class BasicHTTPAuthenticatorTest
|
||||||
new DefaultPasswordProvider("a"),
|
new DefaultPasswordProvider("a"),
|
||||||
new DefaultPasswordProvider("a"),
|
new DefaultPasswordProvider("a"),
|
||||||
false,
|
false,
|
||||||
null,
|
null, null,
|
||||||
|
false,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGoodPassword() throws IOException, ServletException
|
public void testGoodPassword() throws IOException, ServletException
|
||||||
{
|
{
|
||||||
|
@ -107,6 +111,58 @@ public class BasicHTTPAuthenticatorTest
|
||||||
EasyMock.verify(req, resp, filterChain);
|
EasyMock.verify(req, resp, filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGoodPasswordWithValidator() throws IOException, ServletException
|
||||||
|
{
|
||||||
|
CredentialsValidator validator = EasyMock.createMock(CredentialsValidator.class);
|
||||||
|
BasicHTTPAuthenticator authenticatorWithValidator = new BasicHTTPAuthenticator(
|
||||||
|
CACHE_MANAGER_PROVIDER,
|
||||||
|
"basic",
|
||||||
|
"basic",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null, null,
|
||||||
|
false,
|
||||||
|
validator
|
||||||
|
);
|
||||||
|
|
||||||
|
String header = StringUtils.utf8Base64("userA:helloworld");
|
||||||
|
header = StringUtils.format("Basic %s", header);
|
||||||
|
|
||||||
|
EasyMock
|
||||||
|
.expect(
|
||||||
|
validator.validateCredentials(EasyMock.eq("basic"), EasyMock.eq("basic"), EasyMock.eq("userA"), EasyMock.aryEq("helloworld".toCharArray()))
|
||||||
|
)
|
||||||
|
.andReturn(
|
||||||
|
new AuthenticationResult("userA", "basic", "basic", null)
|
||||||
|
)
|
||||||
|
.times(1);
|
||||||
|
EasyMock.replay(validator);
|
||||||
|
|
||||||
|
HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
|
||||||
|
EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
|
||||||
|
req.setAttribute(
|
||||||
|
AuthConfig.DRUID_AUTHENTICATION_RESULT,
|
||||||
|
new AuthenticationResult("userA", "basic", "basic", null)
|
||||||
|
);
|
||||||
|
EasyMock.expectLastCall().times(1);
|
||||||
|
EasyMock.replay(req);
|
||||||
|
|
||||||
|
HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
|
||||||
|
EasyMock.replay(resp);
|
||||||
|
|
||||||
|
FilterChain filterChain = EasyMock.createMock(FilterChain.class);
|
||||||
|
filterChain.doFilter(req, resp);
|
||||||
|
EasyMock.expectLastCall().times(1);
|
||||||
|
EasyMock.replay(filterChain);
|
||||||
|
|
||||||
|
Filter authenticatorFilter = authenticatorWithValidator.getFilter();
|
||||||
|
authenticatorFilter.doFilter(req, resp, filterChain);
|
||||||
|
|
||||||
|
EasyMock.verify(req, resp, validator, filterChain);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadPassword() throws IOException, ServletException
|
public void testBadPassword() throws IOException, ServletException
|
||||||
{
|
{
|
||||||
|
@ -118,7 +174,7 @@ public class BasicHTTPAuthenticatorTest
|
||||||
EasyMock.replay(req);
|
EasyMock.replay(req);
|
||||||
|
|
||||||
HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
|
HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
|
||||||
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User authentication failed username[userA].");
|
||||||
EasyMock.expectLastCall().times(1);
|
EasyMock.expectLastCall().times(1);
|
||||||
EasyMock.replay(resp);
|
EasyMock.replay(resp);
|
||||||
|
|
||||||
|
@ -131,6 +187,53 @@ public class BasicHTTPAuthenticatorTest
|
||||||
EasyMock.verify(req, resp, filterChain);
|
EasyMock.verify(req, resp, filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBadPasswordWithSkipOnFailureValidator() throws IOException, ServletException
|
||||||
|
{
|
||||||
|
CredentialsValidator validator = EasyMock.createMock(CredentialsValidator.class);
|
||||||
|
BasicHTTPAuthenticator authenticatorWithValidator = new BasicHTTPAuthenticator(
|
||||||
|
CACHE_MANAGER_PROVIDER,
|
||||||
|
"basic",
|
||||||
|
"basic",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null, null,
|
||||||
|
true,
|
||||||
|
validator
|
||||||
|
);
|
||||||
|
String header = StringUtils.utf8Base64("userA:badpassword");
|
||||||
|
header = StringUtils.format("Basic %s", header);
|
||||||
|
|
||||||
|
EasyMock
|
||||||
|
.expect(
|
||||||
|
validator.validateCredentials(EasyMock.eq("basic"), EasyMock.eq("basic"), EasyMock.eq("userA"), EasyMock.aryEq("badpassword".toCharArray()))
|
||||||
|
)
|
||||||
|
.andThrow(
|
||||||
|
new BasicSecurityAuthenticationException("User authentication failed username[%s].", "userA")
|
||||||
|
)
|
||||||
|
.times(1);
|
||||||
|
EasyMock.replay(validator);
|
||||||
|
|
||||||
|
HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
|
||||||
|
EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
|
||||||
|
EasyMock.replay(req);
|
||||||
|
|
||||||
|
HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
|
||||||
|
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User authentication failed username[userA].");
|
||||||
|
EasyMock.expectLastCall().times(1);
|
||||||
|
EasyMock.replay(resp);
|
||||||
|
|
||||||
|
// Authentication filter should not move on to the next filter in the chain
|
||||||
|
FilterChain filterChain = EasyMock.createMock(FilterChain.class);
|
||||||
|
EasyMock.replay(filterChain);
|
||||||
|
|
||||||
|
Filter authenticatorFilter = authenticatorWithValidator.getFilter();
|
||||||
|
authenticatorFilter.doFilter(req, resp, filterChain);
|
||||||
|
|
||||||
|
EasyMock.verify(req, resp, validator, filterChain);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnknownUser() throws IOException, ServletException
|
public void testUnknownUser() throws IOException, ServletException
|
||||||
{
|
{
|
||||||
|
@ -155,6 +258,51 @@ public class BasicHTTPAuthenticatorTest
|
||||||
EasyMock.verify(req, resp, filterChain);
|
EasyMock.verify(req, resp, filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnknownUserWithSkipOnFailure() throws IOException, ServletException
|
||||||
|
{
|
||||||
|
CredentialsValidator validator = EasyMock.createMock(CredentialsValidator.class);
|
||||||
|
BasicHTTPAuthenticator authenticatorWithSkipOnFailure = new BasicHTTPAuthenticator(
|
||||||
|
CACHE_MANAGER_PROVIDER,
|
||||||
|
"basic",
|
||||||
|
"basic",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null, null,
|
||||||
|
true,
|
||||||
|
validator
|
||||||
|
);
|
||||||
|
String header = StringUtils.utf8Base64("userB:helloworld");
|
||||||
|
header = StringUtils.format("Basic %s", header);
|
||||||
|
|
||||||
|
HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
|
||||||
|
EasyMock.expect(req.getHeader("Authorization")).andReturn(header);
|
||||||
|
EasyMock.replay(req);
|
||||||
|
|
||||||
|
EasyMock
|
||||||
|
.expect(
|
||||||
|
validator.validateCredentials(EasyMock.eq("basic"), EasyMock.eq("basic"), EasyMock.eq("userB"), EasyMock.aryEq("helloworld".toCharArray()))
|
||||||
|
)
|
||||||
|
.andReturn(null)
|
||||||
|
.times(1);
|
||||||
|
EasyMock.replay(validator);
|
||||||
|
|
||||||
|
HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class);
|
||||||
|
EasyMock.replay(resp);
|
||||||
|
|
||||||
|
// Authentication filter should move on to the next filter in the chain without sending a response
|
||||||
|
FilterChain filterChain = EasyMock.createMock(FilterChain.class);
|
||||||
|
filterChain.doFilter(req, resp);
|
||||||
|
EasyMock.expectLastCall().times(1);
|
||||||
|
EasyMock.replay(filterChain);
|
||||||
|
|
||||||
|
Filter authenticatorFilter = authenticatorWithSkipOnFailure.getFilter();
|
||||||
|
authenticatorFilter.doFilter(req, resp, filterChain);
|
||||||
|
|
||||||
|
EasyMock.verify(req, resp, validator, filterChain);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecognizedButMalformedBasicAuthHeader() throws IOException, ServletException
|
public void testRecognizedButMalformedBasicAuthHeader() throws IOException, ServletException
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,14 +45,13 @@ import java.util.Map;
|
||||||
public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
{
|
{
|
||||||
private static final String AUTHENTICATOR_NAME = "test";
|
private static final String AUTHENTICATOR_NAME = "test";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
private TestDerbyConnector connector;
|
|
||||||
private MetadataStorageTablesConfig tablesConfig;
|
|
||||||
private CoordinatorBasicAuthenticatorMetadataStorageUpdater updater;
|
private CoordinatorBasicAuthenticatorMetadataStorageUpdater updater;
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@ -60,10 +59,9 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
objectMapper = new ObjectMapper(new SmileFactory());
|
objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
connector = derbyConnectorRule.getConnector();
|
TestDerbyConnector connector = derbyConnectorRule.getConnector();
|
||||||
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
MetadataStorageTablesConfig tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
connector.createConfigTable();
|
connector.createConfigTable();
|
||||||
|
|
||||||
updater = new CoordinatorBasicAuthenticatorMetadataStorageUpdater(
|
updater = new CoordinatorBasicAuthenticatorMetadataStorageUpdater(
|
||||||
new AuthenticatorMapper(
|
new AuthenticatorMapper(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
|
@ -76,6 +74,8 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -100,16 +100,24 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
@Test
|
@Test
|
||||||
public void createUser()
|
public void createUser()
|
||||||
{
|
{
|
||||||
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
|
||||||
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of(
|
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of(
|
||||||
"druid", new BasicAuthenticatorUser("druid", null)
|
"druid", new BasicAuthenticatorUser("druid", null)
|
||||||
);
|
);
|
||||||
|
byte[] expectedSerializeUserMap = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, expectedUserMap);
|
||||||
|
|
||||||
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME));
|
||||||
|
|
||||||
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
||||||
);
|
);
|
||||||
Assert.assertEquals(expectedUserMap, actualUserMap);
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
|
||||||
|
// Validate cache user map methods
|
||||||
|
Assert.assertEquals(expectedUserMap, updater.getCachedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCachedSerializedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
|
||||||
// create duplicate should fail
|
// create duplicate should fail
|
||||||
expectedException.expect(BasicSecurityDBResourceException.class);
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
expectedException.expectMessage("User [druid] already exists.");
|
expectedException.expectMessage("User [druid] already exists.");
|
||||||
|
@ -119,15 +127,24 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
@Test
|
@Test
|
||||||
public void deleteUser()
|
public void deleteUser()
|
||||||
{
|
{
|
||||||
|
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of();
|
||||||
|
byte[] expectedSerializeUserMap = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, expectedUserMap);
|
||||||
|
|
||||||
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
updater.createUser(AUTHENTICATOR_NAME, "druid");
|
||||||
updater.deleteUser(AUTHENTICATOR_NAME, "druid");
|
updater.deleteUser(AUTHENTICATOR_NAME, "druid");
|
||||||
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of();
|
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME));
|
||||||
|
|
||||||
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
Map<String, BasicAuthenticatorUser> actualUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME)
|
||||||
);
|
);
|
||||||
Assert.assertEquals(expectedUserMap, actualUserMap);
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
|
|
||||||
|
// Validate cache user map methods
|
||||||
|
Assert.assertEquals(expectedUserMap, updater.getCachedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCachedSerializedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
|
||||||
// delete non-existent user should fail
|
// delete non-existent user should fail
|
||||||
expectedException.expect(BasicSecurityDBResourceException.class);
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
expectedException.expectMessage("User [druid] does not exist.");
|
expectedException.expectMessage("User [druid] does not exist.");
|
||||||
|
@ -153,6 +170,14 @@ public class CoordinatorBasicAuthenticatorMetadataStorageUpdaterTest
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.assertArrayEquals(credentials.getHash(), recalculatedHash);
|
Assert.assertArrayEquals(credentials.getHash(), recalculatedHash);
|
||||||
}
|
|
||||||
|
|
||||||
|
// Validate cache user map methods
|
||||||
|
Map<String, BasicAuthenticatorUser> expectedUserMap = ImmutableMap.of(
|
||||||
|
"druid", new BasicAuthenticatorUser("druid", credentials)
|
||||||
|
);
|
||||||
|
byte[] expectedSerializeUserMap = BasicAuthUtils.serializeAuthenticatorUserMap(objectMapper, expectedUserMap);
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCurrentUserMapBytes(AUTHENTICATOR_NAME));
|
||||||
|
Assert.assertEquals(expectedUserMap, updater.getCachedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
Assert.assertArrayEquals(expectedSerializeUserMap, updater.getCachedSerializedUserMap(AUTHENTICATOR_NAME));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
{
|
{
|
||||||
private static final String AUTHENTICATOR_NAME = "test";
|
private static final String AUTHENTICATOR_NAME = "test";
|
||||||
private static final String AUTHENTICATOR_NAME2 = "test2";
|
private static final String AUTHENTICATOR_NAME2 = "test2";
|
||||||
|
private static final String AUTHENTICATOR_NAME_LDAP = "testLdap";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
@ -60,19 +61,19 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
@Rule
|
@Rule
|
||||||
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
private TestDerbyConnector connector;
|
|
||||||
private MetadataStorageTablesConfig tablesConfig;
|
|
||||||
private BasicAuthenticatorResource resource;
|
private BasicAuthenticatorResource resource;
|
||||||
private CoordinatorBasicAuthenticatorMetadataStorageUpdater storageUpdater;
|
private CoordinatorBasicAuthenticatorMetadataStorageUpdater storageUpdater;
|
||||||
private HttpServletRequest req;
|
private HttpServletRequest req;
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
req = EasyMock.createStrictMock(HttpServletRequest.class);
|
req = EasyMock.createStrictMock(HttpServletRequest.class);
|
||||||
|
|
||||||
connector = derbyConnectorRule.getConnector();
|
objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
TestDerbyConnector connector = derbyConnectorRule.getConnector();
|
||||||
|
MetadataStorageTablesConfig tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
connector.createConfigTable();
|
connector.createConfigTable();
|
||||||
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper(new SmileFactory());
|
ObjectMapper objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
|
@ -83,22 +84,39 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
new BasicHTTPAuthenticator(
|
new BasicHTTPAuthenticator(
|
||||||
null,
|
null,
|
||||||
AUTHENTICATOR_NAME,
|
AUTHENTICATOR_NAME,
|
||||||
"test",
|
null,
|
||||||
new DefaultPasswordProvider("druid"),
|
new DefaultPasswordProvider("druid"),
|
||||||
new DefaultPasswordProvider("druid"),
|
new DefaultPasswordProvider("druid"),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
AUTHENTICATOR_NAME2,
|
AUTHENTICATOR_NAME2,
|
||||||
new BasicHTTPAuthenticator(
|
new BasicHTTPAuthenticator(
|
||||||
null,
|
null,
|
||||||
AUTHENTICATOR_NAME2,
|
AUTHENTICATOR_NAME2,
|
||||||
"test",
|
null,
|
||||||
new DefaultPasswordProvider("druid"),
|
new DefaultPasswordProvider("druid"),
|
||||||
new DefaultPasswordProvider("druid"),
|
new DefaultPasswordProvider("druid"),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
AUTHENTICATOR_NAME_LDAP,
|
||||||
|
new BasicHTTPAuthenticator(
|
||||||
|
null,
|
||||||
|
AUTHENTICATOR_NAME2,
|
||||||
|
null,
|
||||||
|
new DefaultPasswordProvider("druid"),
|
||||||
|
new DefaultPasswordProvider("druid"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -164,10 +182,26 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(expectedUsers, response.getEntity());
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
|
||||||
|
// Verify cached user map is also getting updated
|
||||||
|
response = resource.getCachedSerializedUserMap(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getEntity() instanceof byte[]);
|
||||||
|
Map<String, BasicAuthenticatorUser> cachedUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(objectMapper, (byte[]) response.getEntity());
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.INTERNAL_USER_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid").getName(), "druid");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid2"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid2").getName(), "druid2");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid3"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid3").getName(), "druid3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSeparateDatabaseTables()
|
public void testGetAllUsersSeparateDatabaseTables()
|
||||||
{
|
{
|
||||||
Response response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
Response response = resource.getAllUsers(req, AUTHENTICATOR_NAME);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
@ -201,9 +235,43 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(expectedUsers, response.getEntity());
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
|
|
||||||
|
// Verify cached user map for AUTHENTICATOR_NAME authenticator is also getting updated
|
||||||
|
response = resource.getCachedSerializedUserMap(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getEntity() instanceof byte[]);
|
||||||
|
|
||||||
|
Map<String, BasicAuthenticatorUser> cachedUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(objectMapper, (byte[]) response.getEntity());
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.INTERNAL_USER_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid").getName(), "druid");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid2"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid2").getName(), "druid2");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid3"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid3").getName(), "druid3");
|
||||||
|
|
||||||
response = resource.getAllUsers(req, AUTHENTICATOR_NAME2);
|
response = resource.getAllUsers(req, AUTHENTICATOR_NAME2);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(expectedUsers2, response.getEntity());
|
Assert.assertEquals(expectedUsers2, response.getEntity());
|
||||||
|
|
||||||
|
// Verify cached user map for each AUTHENTICATOR_NAME2 is also getting updated
|
||||||
|
response = resource.getCachedSerializedUserMap(req, AUTHENTICATOR_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getEntity() instanceof byte[]);
|
||||||
|
|
||||||
|
cachedUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(objectMapper, (byte[]) response.getEntity());
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get(BasicAuthUtils.INTERNAL_USER_NAME));
|
||||||
|
Assert.assertEquals(cachedUserMap.get(BasicAuthUtils.ADMIN_NAME).getName(), BasicAuthUtils.ADMIN_NAME);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid4"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid4").getName(), "druid4");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid5"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid5").getName(), "druid5");
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid6"));
|
||||||
|
Assert.assertEquals(cachedUserMap.get("druid6").getName(), "druid6");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -220,6 +288,13 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getCachedSerializedUserMap(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getEntity() instanceof byte[]);
|
||||||
|
Map<String, BasicAuthenticatorUser> cachedUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(objectMapper, (byte[]) response.getEntity());
|
||||||
|
Assert.assertNotNull(cachedUserMap);
|
||||||
|
Assert.assertNull(cachedUserMap.get("druid"));
|
||||||
|
|
||||||
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
Assert.assertEquals(400, response.getStatus());
|
Assert.assertEquals(400, response.getStatus());
|
||||||
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
|
@ -263,6 +338,29 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
);
|
);
|
||||||
Assert.assertArrayEquals(recalculatedHash, hash);
|
Assert.assertArrayEquals(recalculatedHash, hash);
|
||||||
|
|
||||||
|
response = resource.getCachedSerializedUserMap(req, AUTHENTICATOR_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getEntity() instanceof byte[]);
|
||||||
|
Map<String, BasicAuthenticatorUser> cachedUserMap = BasicAuthUtils.deserializeAuthenticatorUserMap(objectMapper, (byte[]) response.getEntity());
|
||||||
|
Assert.assertNotNull(cachedUserMap);
|
||||||
|
Assert.assertNotNull(cachedUserMap.get("druid"));
|
||||||
|
Assert.assertEquals("druid", cachedUserMap.get("druid").getName());
|
||||||
|
BasicAuthenticatorCredentials cachedUserCredentials = cachedUserMap.get("druid").getCredentials();
|
||||||
|
|
||||||
|
salt = cachedUserCredentials.getSalt();
|
||||||
|
hash = cachedUserCredentials.getHash();
|
||||||
|
iterations = cachedUserCredentials.getIterations();
|
||||||
|
Assert.assertEquals(BasicAuthUtils.SALT_LENGTH, salt.length);
|
||||||
|
Assert.assertEquals(BasicAuthUtils.KEY_LENGTH / 8, hash.length);
|
||||||
|
Assert.assertEquals(BasicAuthUtils.DEFAULT_KEY_ITERATIONS, iterations);
|
||||||
|
|
||||||
|
recalculatedHash = BasicAuthUtils.hashPassword(
|
||||||
|
"helloworld".toCharArray(),
|
||||||
|
salt,
|
||||||
|
iterations
|
||||||
|
);
|
||||||
|
Assert.assertArrayEquals(recalculatedHash, hash);
|
||||||
|
|
||||||
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
response = resource.deleteUser(req, AUTHENTICATOR_NAME, "druid");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
@ -284,5 +382,4 @@ public class CoordinatorBasicAuthenticatorResourceTest
|
||||||
{
|
{
|
||||||
return ImmutableMap.of("error", errorMsg);
|
return ImmutableMap.of("error", errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticato
|
||||||
public class NoopBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
public class NoopBasicAuthenticatorCacheNotifier implements BasicAuthenticatorCacheNotifier
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
public void addUserUpdate(String updatedAuthenticatorPrefix, byte[] updatedUserMap)
|
||||||
{
|
{
|
||||||
|
// Do nothing as this is a noop implementation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.druid.security.authentication.validator;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import org.apache.druid.java.util.common.IAE;
|
||||||
|
import org.apache.druid.security.basic.BasicSecurityAuthenticationException;
|
||||||
|
import org.apache.druid.security.basic.authentication.db.cache.BasicAuthenticatorCacheManager;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentialUpdate;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorCredentials;
|
||||||
|
import org.apache.druid.security.basic.authentication.entity.BasicAuthenticatorUser;
|
||||||
|
import org.apache.druid.security.basic.authentication.validator.MetadataStoreCredentialsValidator;
|
||||||
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DBCredentialsValidatorTest
|
||||||
|
{
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
private static BasicAuthenticatorCredentials USER_A_CREDENTIALS = new BasicAuthenticatorCredentials(
|
||||||
|
new BasicAuthenticatorCredentialUpdate("helloworld", 20)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static Provider<BasicAuthenticatorCacheManager> CACHE_MANAGER_PROVIDER = Providers.of(
|
||||||
|
new BasicAuthenticatorCacheManager()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handleAuthenticatorUserMapUpdate(String authenticatorPrefix, byte[] serializedUserMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, BasicAuthenticatorUser> getUserMap(String authenticatorPrefix)
|
||||||
|
{
|
||||||
|
return ImmutableMap.of(
|
||||||
|
"userA", new BasicAuthenticatorUser("userA", USER_A_CREDENTIALS),
|
||||||
|
"userB", new BasicAuthenticatorUser("userB", null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private static MetadataStoreCredentialsValidator validator = new MetadataStoreCredentialsValidator(CACHE_MANAGER_PROVIDER);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateBadAuthenticator()
|
||||||
|
{
|
||||||
|
String authenticatorName = "notbasic";
|
||||||
|
String authorizerName = "basic";
|
||||||
|
String username = "userA";
|
||||||
|
String password = "helloworld";
|
||||||
|
|
||||||
|
BasicAuthenticatorCacheManager cacheManager = EasyMock.createMock(BasicAuthenticatorCacheManager.class);
|
||||||
|
EasyMock.expect(cacheManager.getUserMap(authenticatorName)).andReturn(null).times(1);
|
||||||
|
EasyMock.replay(cacheManager);
|
||||||
|
|
||||||
|
MetadataStoreCredentialsValidator validator = new MetadataStoreCredentialsValidator(Providers.of(cacheManager));
|
||||||
|
|
||||||
|
expectedException.expect(IAE.class);
|
||||||
|
expectedException.expectMessage("No userMap is available for authenticator with prefix: [notbasic]");
|
||||||
|
validator.validateCredentials(authenticatorName, authorizerName, username, password.toCharArray());
|
||||||
|
|
||||||
|
EasyMock.verify(cacheManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateMissingCredentials()
|
||||||
|
{
|
||||||
|
String authenticatorName = "basic";
|
||||||
|
String authorizerName = "basic";
|
||||||
|
String username = "userB";
|
||||||
|
String password = "helloworld";
|
||||||
|
|
||||||
|
AuthenticationResult result = validator.validateCredentials(authenticatorName, authorizerName, username, password.toCharArray());
|
||||||
|
Assert.assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateMissingUser()
|
||||||
|
{
|
||||||
|
String authenticatorName = "basic";
|
||||||
|
String authorizerName = "basic";
|
||||||
|
String username = "userC";
|
||||||
|
String password = "helloworld";
|
||||||
|
|
||||||
|
AuthenticationResult result = validator.validateCredentials(authenticatorName, authorizerName, username, password.toCharArray());
|
||||||
|
Assert.assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateGoodCredentials()
|
||||||
|
{
|
||||||
|
String authenticatorName = "basic";
|
||||||
|
String authorizerName = "basic";
|
||||||
|
String username = "userA";
|
||||||
|
String password = "helloworld";
|
||||||
|
|
||||||
|
AuthenticationResult result = validator.validateCredentials(authenticatorName, authorizerName, username, password.toCharArray());
|
||||||
|
|
||||||
|
Assert.assertNotNull(result);
|
||||||
|
Assert.assertEquals(username, result.getIdentity());
|
||||||
|
Assert.assertEquals(authenticatorName, result.getAuthenticatedBy());
|
||||||
|
Assert.assertEquals(authorizerName, result.getAuthorizerName());
|
||||||
|
Assert.assertNull(result.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateBadCredentials()
|
||||||
|
{
|
||||||
|
String authenticatorName = "basic";
|
||||||
|
String authorizerName = "basic";
|
||||||
|
String username = "userA";
|
||||||
|
String password = "badpassword";
|
||||||
|
|
||||||
|
expectedException.expect(BasicSecurityAuthenticationException.class);
|
||||||
|
expectedException.expectMessage("User metadata store authentication failed username[userA].");
|
||||||
|
validator.validateCredentials(authenticatorName, authorizerName, username, password.toCharArray());
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,9 +25,13 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import org.apache.druid.metadata.MetadataStorageTablesConfig;
|
import org.apache.druid.metadata.MetadataStorageTablesConfig;
|
||||||
import org.apache.druid.metadata.TestDerbyConnector;
|
import org.apache.druid.metadata.TestDerbyConnector;
|
||||||
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
|
||||||
|
import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
|
import org.apache.druid.security.basic.authorization.LDAPRoleProvider;
|
||||||
|
import org.apache.druid.security.basic.authorization.MetadataStoreRoleProvider;
|
||||||
import org.apache.druid.security.basic.authorization.db.cache.MetadataStoragePollingBasicAuthorizerCacheManager;
|
import org.apache.druid.security.basic.authorization.db.cache.MetadataStoragePollingBasicAuthorizerCacheManager;
|
||||||
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.server.security.Access;
|
import org.apache.druid.server.security.Access;
|
||||||
import org.apache.druid.server.security.Action;
|
import org.apache.druid.server.security.Action;
|
||||||
import org.apache.druid.server.security.AuthenticationResult;
|
import org.apache.druid.server.security.AuthenticationResult;
|
||||||
|
@ -41,37 +45,75 @@ import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.naming.directory.BasicAttribute;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class BasicRoleBasedAuthorizerTest
|
public class BasicRoleBasedAuthorizerTest
|
||||||
{
|
{
|
||||||
private static final String AUTHORIZER_NAME = "test";
|
private static final String DB_AUTHORIZER_NAME = "metadata";
|
||||||
|
private static final String LDAP_AUTHORIZER_NAME = "ldap";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
private BasicRoleBasedAuthorizer authorizer;
|
private BasicRoleBasedAuthorizer authorizer;
|
||||||
private TestDerbyConnector connector;
|
private BasicRoleBasedAuthorizer ldapAuthorizer;
|
||||||
private MetadataStorageTablesConfig tablesConfig;
|
|
||||||
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
||||||
|
private String[] groupFilters = {
|
||||||
|
"*,OU=Druid,OU=Application,OU=Groupings,DC=corp,DC=apache,DC=org",
|
||||||
|
"*,OU=Platform,OU=Groupings,DC=corp,DC=apache,DC=org"
|
||||||
|
};
|
||||||
|
|
||||||
|
private SearchResult userSearchResult;
|
||||||
|
private SearchResult adminSearchResult;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
connector = derbyConnectorRule.getConnector();
|
TestDerbyConnector connector = derbyConnectorRule.getConnector();
|
||||||
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
MetadataStorageTablesConfig tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
connector.createConfigTable();
|
connector.createConfigTable();
|
||||||
|
|
||||||
|
BasicAttributes userAttrs = new BasicAttributes(true);
|
||||||
|
userAttrs.put(new BasicAttribute("sAMAccountName", "druiduser"));
|
||||||
|
userAttrs.put(new BasicAttribute("memberOf", "CN=user,OU=Druid,OU=Application,OU=Groupings,DC=corp,DC=apache,DC=org"));
|
||||||
|
|
||||||
|
BasicAttributes adminAttrs = new BasicAttributes(true);
|
||||||
|
adminAttrs.put(new BasicAttribute("sAMAccountName", "druidadmin"));
|
||||||
|
adminAttrs.put(new BasicAttribute("memberOf", "CN=admin,OU=Platform,OU=Groupings,DC=corp,DC=apache,DC=org"));
|
||||||
|
|
||||||
|
userSearchResult = new SearchResult("CN=1234,OU=Employees,OU=People", null, userAttrs);
|
||||||
|
adminSearchResult = new SearchResult("CN=9876,OU=Employees,OU=People", null, adminAttrs);
|
||||||
|
|
||||||
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
new AuthorizerMapper(
|
new AuthorizerMapper(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
AUTHORIZER_NAME,
|
DB_AUTHORIZER_NAME,
|
||||||
new BasicRoleBasedAuthorizer(
|
new BasicRoleBasedAuthorizer(
|
||||||
null,
|
null,
|
||||||
AUTHORIZER_NAME,
|
DB_AUTHORIZER_NAME,
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
|
null, null,
|
||||||
|
null,
|
||||||
|
new MetadataStoreRoleProvider(null)
|
||||||
|
),
|
||||||
|
LDAP_AUTHORIZER_NAME,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
LDAP_AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null, null,
|
||||||
|
null,
|
||||||
|
new LDAPRoleProvider(null, groupFilters)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -86,12 +128,23 @@ public class BasicRoleBasedAuthorizerTest
|
||||||
updater.start();
|
updater.start();
|
||||||
|
|
||||||
authorizer = new BasicRoleBasedAuthorizer(
|
authorizer = new BasicRoleBasedAuthorizer(
|
||||||
new MetadataStoragePollingBasicAuthorizerCacheManager(
|
|
||||||
updater
|
|
||||||
),
|
|
||||||
AUTHORIZER_NAME,
|
|
||||||
null,
|
null,
|
||||||
null
|
DB_AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null, null,
|
||||||
|
null,
|
||||||
|
new MetadataStoreRoleProvider(new MetadataStoragePollingBasicAuthorizerCacheManager(updater))
|
||||||
|
);
|
||||||
|
|
||||||
|
ldapAuthorizer = new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
LDAP_AUTHORIZER_NAME,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null, null,
|
||||||
|
null,
|
||||||
|
new LDAPRoleProvider(new MetadataStoragePollingBasicAuthorizerCacheManager(updater), groupFilters)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,15 +156,15 @@ public class BasicRoleBasedAuthorizerTest
|
||||||
@Test
|
@Test
|
||||||
public void testAuth()
|
public void testAuth()
|
||||||
{
|
{
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(DB_AUTHORIZER_NAME, "druid");
|
||||||
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
updater.createRole(DB_AUTHORIZER_NAME, "druidRole");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.assignUserRole(DB_AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
List<ResourceAction> permissions = Collections.singletonList(
|
List<ResourceAction> permissions = Collections.singletonList(
|
||||||
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE)
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE)
|
||||||
);
|
);
|
||||||
|
|
||||||
updater.setPermissions(AUTHORIZER_NAME, "druidRole", permissions);
|
updater.setPermissions(DB_AUTHORIZER_NAME, "druidRole", permissions);
|
||||||
|
|
||||||
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null);
|
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null);
|
||||||
|
|
||||||
|
@ -129,4 +182,242 @@ public class BasicRoleBasedAuthorizerTest
|
||||||
);
|
);
|
||||||
Assert.assertFalse(access.isAllowed());
|
Assert.assertFalse(access.isAllowed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthGroupMapping()
|
||||||
|
{
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = new BasicAuthorizerGroupMapping("druidGroupMapping", "CN=admin,OU=Platform,OU=Groupings,DC=corp,DC=apache,DC=org", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, groupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> permissions = Collections.singletonList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "druidRole", permissions);
|
||||||
|
|
||||||
|
Map<String, Object> contexMap = new HashMap<>();
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, adminSearchResult);
|
||||||
|
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult("druidadmin", "druid", null, contexMap);
|
||||||
|
|
||||||
|
Access access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testAuthGroupMappingPatternRightMask()
|
||||||
|
{
|
||||||
|
//Admin
|
||||||
|
BasicAuthorizerGroupMapping adminGrroupMapping = new BasicAuthorizerGroupMapping("adminGrroupMapping", "CN=admin,*", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, adminGrroupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "adminDruidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "adminGrroupMapping", "adminDruidRole");
|
||||||
|
List<ResourceAction> adminPermissions = Arrays.asList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.READ)
|
||||||
|
);
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "adminDruidRole", adminPermissions);
|
||||||
|
|
||||||
|
//User
|
||||||
|
BasicAuthorizerGroupMapping userGrroupMapping = new BasicAuthorizerGroupMapping("userGrroupMapping", "CN=user,*", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, userGrroupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "userDruidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "userGrroupMapping", "userDruidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> userPermissions = Collections.singletonList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.READ)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "userDruidRole", userPermissions);
|
||||||
|
|
||||||
|
Map<String, Object> contexMap = new HashMap<>();
|
||||||
|
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, adminSearchResult);
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult("druidadmin", "druid", null, contexMap);
|
||||||
|
|
||||||
|
Access access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
contexMap = new HashMap<>();
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, userSearchResult);
|
||||||
|
authenticationResult = new AuthenticationResult("druiduser", "druid", null, contexMap);
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthGroupMappingPatternLeftMask()
|
||||||
|
{
|
||||||
|
//Admin
|
||||||
|
BasicAuthorizerGroupMapping adminGrroupMapping = new BasicAuthorizerGroupMapping("adminGrroupMapping", "*,CN=admin,OU=Platform,OU=Groupings,DC=corp,DC=apache,DC=org", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, adminGrroupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "adminDruidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "adminGrroupMapping", "adminDruidRole");
|
||||||
|
List<ResourceAction> adminPermissions = Arrays.asList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.READ)
|
||||||
|
);
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "adminDruidRole", adminPermissions);
|
||||||
|
|
||||||
|
//User
|
||||||
|
BasicAuthorizerGroupMapping userGrroupMapping = new BasicAuthorizerGroupMapping("userGrroupMapping", "*,CN=user,OU=Druid,OU=Application,OU=Groupings,DC=corp,DC=apache,DC=org", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, userGrroupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "userDruidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "userGrroupMapping", "userDruidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> userPermissions = Collections.singletonList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.READ)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "userDruidRole", userPermissions);
|
||||||
|
|
||||||
|
Map<String, Object> contexMap = new HashMap<>();
|
||||||
|
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, adminSearchResult);
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult("druidadmin", "druid", null, contexMap);
|
||||||
|
|
||||||
|
Access access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
contexMap = new HashMap<>();
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, userSearchResult);
|
||||||
|
authenticationResult = new AuthenticationResult("druiduser", "druid", null, contexMap);
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertTrue(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthMissingGroupMapping()
|
||||||
|
{
|
||||||
|
BasicAuthorizerGroupMapping groupMapping = new BasicAuthorizerGroupMapping("druidGroupMapping", "CN=unknown,*", null);
|
||||||
|
updater.createGroupMapping(LDAP_AUTHORIZER_NAME, groupMapping);
|
||||||
|
updater.createRole(LDAP_AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignGroupMappingRole(LDAP_AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
|
||||||
|
List<ResourceAction> permissions = Arrays.asList(
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.WRITE),
|
||||||
|
new ResourceAction(new Resource("testResource", ResourceType.DATASOURCE), Action.READ)
|
||||||
|
);
|
||||||
|
|
||||||
|
updater.setPermissions(LDAP_AUTHORIZER_NAME, "druidRole", permissions);
|
||||||
|
|
||||||
|
Map<String, Object> contexMap = new HashMap<>();
|
||||||
|
contexMap.put(BasicAuthUtils.SEARCH_RESULT_CONTEXT_KEY, userSearchResult);
|
||||||
|
|
||||||
|
AuthenticationResult authenticationResult = new AuthenticationResult("druiduser", "druid", null, contexMap);
|
||||||
|
|
||||||
|
Access access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("testResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.WRITE
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
|
||||||
|
access = ldapAuthorizer.authorize(
|
||||||
|
authenticationResult,
|
||||||
|
new Resource("wrongResource", ResourceType.DATASOURCE),
|
||||||
|
Action.READ
|
||||||
|
);
|
||||||
|
Assert.assertFalse(access.isAllowed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.druid.security.basic.BasicAuthUtils;
|
||||||
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
|
||||||
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
|
||||||
|
@ -80,8 +81,6 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
@Rule
|
@Rule
|
||||||
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
public final TestDerbyConnector.DerbyConnectorRule derbyConnectorRule = new TestDerbyConnector.DerbyConnectorRule();
|
||||||
|
|
||||||
private TestDerbyConnector connector;
|
|
||||||
private MetadataStorageTablesConfig tablesConfig;
|
|
||||||
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
private CoordinatorBasicAuthorizerMetadataStorageUpdater updater;
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@ -89,8 +88,8 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
objectMapper = new ObjectMapper(new SmileFactory());
|
objectMapper = new ObjectMapper(new SmileFactory());
|
||||||
connector = derbyConnectorRule.getConnector();
|
TestDerbyConnector connector = derbyConnectorRule.getConnector();
|
||||||
tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
MetadataStorageTablesConfig tablesConfig = derbyConnectorRule.metadataTablesConfigSupplier().get();
|
||||||
connector.createConfigTable();
|
connector.createConfigTable();
|
||||||
|
|
||||||
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
updater = new CoordinatorBasicAuthorizerMetadataStorageUpdater(
|
||||||
|
@ -101,6 +100,10 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
null,
|
null,
|
||||||
AUTHORIZER_NAME,
|
AUTHORIZER_NAME,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -138,6 +141,27 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
Assert.assertEquals(expectedUserMap, actualUserMap);
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteGroupMapping()
|
||||||
|
{
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> expectedGroupMappingMap = new HashMap<>();
|
||||||
|
expectedGroupMappingMap.put("druid", new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> actualGroupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentGroupMappingMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMappingMap, actualGroupMappingMap);
|
||||||
|
|
||||||
|
updater.deleteGroupMapping(AUTHORIZER_NAME, "druid");
|
||||||
|
expectedGroupMappingMap.remove("druid");
|
||||||
|
actualGroupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentGroupMappingMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMappingMap, actualGroupMappingMap);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteNonExistentUser()
|
public void testDeleteNonExistentUser()
|
||||||
{
|
{
|
||||||
|
@ -146,6 +170,15 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
updater.deleteUser(AUTHORIZER_NAME, "druid");
|
updater.deleteUser(AUTHORIZER_NAME, "druid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteNonExistentGroupMapping()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [druid] does not exist.");
|
||||||
|
updater.deleteGroupMapping(AUTHORIZER_NAME, "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateDuplicateUser()
|
public void testCreateDuplicateUser()
|
||||||
{
|
{
|
||||||
|
@ -155,6 +188,14 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDuplicateGroupMapping()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [druid] already exists.");
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
}
|
||||||
// role tests
|
// role tests
|
||||||
@Test
|
@Test
|
||||||
public void testCreateDeleteRole()
|
public void testCreateDeleteRole()
|
||||||
|
@ -194,13 +235,13 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
updater.createRole(AUTHORIZER_NAME, "druid");
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// role and user tests
|
// role, user, and group mapping tests
|
||||||
@Test
|
@Test
|
||||||
public void testAddAndRemoveRole()
|
public void testAddAndRemoveRoleToUser()
|
||||||
{
|
{
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.assignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
Map<String, BasicAuthorizerUser> expectedUserMap = new HashMap<>(BASE_USER_MAP);
|
Map<String, BasicAuthorizerUser> expectedUserMap = new HashMap<>(BASE_USER_MAP);
|
||||||
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of("druidRole")));
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of("druidRole")));
|
||||||
|
@ -221,7 +262,7 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
Assert.assertEquals(expectedUserMap, actualUserMap);
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
updater.unassignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.unassignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of()));
|
expectedUserMap.put("druid", new BasicAuthorizerUser("druid", ImmutableSet.of()));
|
||||||
actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
actualUserMap = BasicAuthUtils.deserializeAuthorizerUserMap(
|
||||||
objectMapper,
|
objectMapper,
|
||||||
|
@ -232,13 +273,60 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// role, user, and group mapping tests
|
||||||
|
@Test
|
||||||
|
public void testAddAndRemoveRoleToGroupMapping()
|
||||||
|
{
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> expectedGroupMappingMap = new HashMap<>();
|
||||||
|
expectedGroupMappingMap.put("druid", new BasicAuthorizerGroupMapping("druid", "CN=test", ImmutableSet.of("druidRole")));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = new HashMap<>(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put("druidRole", new BasicAuthorizerRole("druidRole", ImmutableList.of()));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> actualGroupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentGroupMappingMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedGroupMappingMap, actualGroupMappingMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.unassignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
expectedGroupMappingMap.put("druid", new BasicAuthorizerGroupMapping("druid", "CN=test", ImmutableSet.of()));
|
||||||
|
actualGroupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentGroupMappingMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedGroupMappingMap, actualGroupMappingMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddRoleToNonExistentUser()
|
public void testAddRoleToNonExistentUser()
|
||||||
{
|
{
|
||||||
expectedException.expect(BasicSecurityDBResourceException.class);
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
expectedException.expectMessage("User [nonUser] does not exist.");
|
expectedException.expectMessage("User [nonUser] does not exist.");
|
||||||
updater.createRole(AUTHORIZER_NAME, "druid");
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "nonUser", "druid");
|
updater.assignUserRole(AUTHORIZER_NAME, "nonUser", "druid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRoleToNonExistentGroupMapping()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [nonUser] does not exist.");
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druid");
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "nonUser", "druid");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -247,7 +335,16 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
expectedException.expect(BasicSecurityDBResourceException.class);
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
expectedException.expectMessage("Role [nonRole] does not exist.");
|
expectedException.expectMessage("Role [nonRole] does not exist.");
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "nonRole");
|
updater.assignUserRole(AUTHORIZER_NAME, "druid", "nonRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddNonexistentRoleToGroupMapping()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Role [nonRole] does not exist.");
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "druid", "nonRole");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -257,12 +354,33 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
expectedException.expectMessage("User [druid] already has role [druidRole].");
|
expectedException.expectMessage("User [druid] already has role [druidRole].");
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.assignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.assignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnassignInvalidRoleAssignmentFails()
|
public void testAddExistingRoleToGroupMappingFails()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [druid] already has role [druidRole].");
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddExistingRoleToGroupMappingWithRoleFails()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [druid] already has role [druidRole].");
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", ImmutableSet.of("druidRole")));
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
updater.assignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnassignInvalidRoleAssignmentToUserFails()
|
||||||
{
|
{
|
||||||
expectedException.expect(BasicSecurityDBResourceException.class);
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
expectedException.expectMessage("User [druid] does not have role [druidRole].");
|
expectedException.expectMessage("User [druid] does not have role [druidRole].");
|
||||||
|
@ -289,16 +407,49 @@ public class CoordinatorBasicAuthorizerMetadataStorageUpdaterTest
|
||||||
Assert.assertEquals(expectedUserMap, actualUserMap);
|
Assert.assertEquals(expectedUserMap, actualUserMap);
|
||||||
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
updater.unassignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.unassignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnassignInvalidRoleAssignmentToGroupMappingFails()
|
||||||
|
{
|
||||||
|
expectedException.expect(BasicSecurityDBResourceException.class);
|
||||||
|
expectedException.expectMessage("Group mapping [druid] does not have role [druidRole].");
|
||||||
|
|
||||||
|
|
||||||
|
updater.createGroupMapping(AUTHORIZER_NAME, new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> expectedGroupMappingMap = new HashMap<>();
|
||||||
|
expectedGroupMappingMap.put("druid", new BasicAuthorizerGroupMapping("druid", "CN=test", null));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> expectedRoleMap = new HashMap<>(BASE_ROLE_MAP);
|
||||||
|
expectedRoleMap.put("druidRole", new BasicAuthorizerRole("druidRole", ImmutableList.of()));
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerGroupMapping> actualGroupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentGroupMappingMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, BasicAuthorizerRole> actualRoleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
|
||||||
|
objectMapper,
|
||||||
|
updater.getCurrentRoleMapBytes(AUTHORIZER_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedGroupMappingMap, actualGroupMappingMap);
|
||||||
|
Assert.assertEquals(expectedRoleMap, actualRoleMap);
|
||||||
|
|
||||||
|
updater.unassignGroupMappingRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// role and permission tests
|
// role and permission tests
|
||||||
@Test
|
@Test
|
||||||
public void testSetRolePermissions()
|
public void testSetRolePermissions()
|
||||||
{
|
{
|
||||||
updater.createUser(AUTHORIZER_NAME, "druid");
|
updater.createUser(AUTHORIZER_NAME, "druid");
|
||||||
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
updater.createRole(AUTHORIZER_NAME, "druidRole");
|
||||||
updater.assignRole(AUTHORIZER_NAME, "druid", "druidRole");
|
updater.assignUserRole(AUTHORIZER_NAME, "druid", "druidRole");
|
||||||
|
|
||||||
List<ResourceAction> permsToAdd = ImmutableList.of(
|
List<ResourceAction> permsToAdd = ImmutableList.of(
|
||||||
new ResourceAction(
|
new ResourceAction(
|
||||||
|
|
|
@ -35,6 +35,8 @@ import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
|
||||||
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
import org.apache.druid.security.basic.authorization.db.updater.CoordinatorBasicAuthorizerMetadataStorageUpdater;
|
||||||
import org.apache.druid.security.basic.authorization.endpoint.BasicAuthorizerResource;
|
import org.apache.druid.security.basic.authorization.endpoint.BasicAuthorizerResource;
|
||||||
import org.apache.druid.security.basic.authorization.endpoint.CoordinatorBasicAuthorizerResourceHandler;
|
import org.apache.druid.security.basic.authorization.endpoint.CoordinatorBasicAuthorizerResourceHandler;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
|
||||||
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMappingFull;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
|
||||||
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull;
|
||||||
|
@ -70,6 +72,7 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
{
|
{
|
||||||
private static final String AUTHORIZER_NAME = "test";
|
private static final String AUTHORIZER_NAME = "test";
|
||||||
private static final String AUTHORIZER_NAME2 = "test2";
|
private static final String AUTHORIZER_NAME2 = "test2";
|
||||||
|
private static final String AUTHORIZER_NAME3 = "test3";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
@ -99,6 +102,10 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
null,
|
null,
|
||||||
AUTHORIZER_NAME,
|
AUTHORIZER_NAME,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
AUTHORIZER_NAME2,
|
AUTHORIZER_NAME2,
|
||||||
|
@ -106,6 +113,21 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
null,
|
null,
|
||||||
AUTHORIZER_NAME2,
|
AUTHORIZER_NAME2,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
AUTHORIZER_NAME3,
|
||||||
|
new BasicRoleBasedAuthorizer(
|
||||||
|
null,
|
||||||
|
AUTHORIZER_NAME3,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"adminGroupMapping",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -148,13 +170,47 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response.getEntity()
|
response.getEntity()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
response = resource.getAllUsers(req, AUTHORIZER_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.INTERNAL_USER_NAME),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME3);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of("adminGroupMapping"),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
resource.createUser(req, AUTHORIZER_NAME, "druid");
|
resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
resource.createUser(req, AUTHORIZER_NAME, "druid3");
|
resource.createUser(req, AUTHORIZER_NAME, "druid3");
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", new BasicAuthorizerGroupMapping("druid2GroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druid3GroupMapping", new BasicAuthorizerGroupMapping("druid3GroupMapping", "", new HashSet<>()));
|
||||||
|
|
||||||
resource.createUser(req, AUTHORIZER_NAME2, "druid4");
|
resource.createUser(req, AUTHORIZER_NAME2, "druid4");
|
||||||
resource.createUser(req, AUTHORIZER_NAME2, "druid5");
|
resource.createUser(req, AUTHORIZER_NAME2, "druid5");
|
||||||
resource.createUser(req, AUTHORIZER_NAME2, "druid6");
|
resource.createUser(req, AUTHORIZER_NAME2, "druid6");
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME2, "druid4GroupMapping", new BasicAuthorizerGroupMapping("druid4GroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME2, "druid5GroupMapping", new BasicAuthorizerGroupMapping("druid5GroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME2, "druid6GroupMapping", new BasicAuthorizerGroupMapping("druid6GroupMapping", "", new HashSet<>()));
|
||||||
|
|
||||||
Set<String> expectedUsers = ImmutableSet.of(
|
Set<String> expectedUsers = ImmutableSet.of(
|
||||||
BasicAuthUtils.ADMIN_NAME,
|
BasicAuthUtils.ADMIN_NAME,
|
||||||
|
@ -179,6 +235,26 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response = resource.getAllUsers(req, AUTHORIZER_NAME2);
|
response = resource.getAllUsers(req, AUTHORIZER_NAME2);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(expectedUsers2, response.getEntity());
|
Assert.assertEquals(expectedUsers2, response.getEntity());
|
||||||
|
|
||||||
|
Set<String> expectedGroupMappings = ImmutableSet.of(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"druid2GroupMapping",
|
||||||
|
"druid3GroupMapping"
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> expectedGroupMappings2 = ImmutableSet.of(
|
||||||
|
"druid4GroupMapping",
|
||||||
|
"druid5GroupMapping",
|
||||||
|
"druid6GroupMapping"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedGroupMappings, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME2);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedGroupMappings2, response.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -219,6 +295,31 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
Assert.assertEquals(expectedUsers, response.getEntity());
|
Assert.assertEquals(expectedUsers, response.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAllGroupMappings()
|
||||||
|
{
|
||||||
|
Response response = resource.getAllGroupMappings(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
ImmutableSet.of(),
|
||||||
|
response.getEntity()
|
||||||
|
);
|
||||||
|
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", new BasicAuthorizerGroupMapping("druid2GroupMapping", "", new HashSet<>()));
|
||||||
|
resource.createGroupMapping(req, AUTHORIZER_NAME, "druid3GroupMapping", new BasicAuthorizerGroupMapping("druid3GroupMapping", "", new HashSet<>()));
|
||||||
|
|
||||||
|
Set<String> expectedGroupMappings = ImmutableSet.of(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"druid2GroupMapping",
|
||||||
|
"druid3GroupMapping"
|
||||||
|
);
|
||||||
|
|
||||||
|
response = resource.getAllGroupMappings(req, AUTHORIZER_NAME);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedGroupMappings, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllRoles()
|
public void testGetAllRoles()
|
||||||
{
|
{
|
||||||
|
@ -273,6 +374,33 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDeleteGroupMapping()
|
||||||
|
{
|
||||||
|
Response response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMapping expectedGroupMapping = new BasicAuthorizerGroupMapping(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"", ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.deleteGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.deleteGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping");
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("Group mapping [druidGroupMapping] does not exist."), response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(400, response.getStatus());
|
||||||
|
Assert.assertEquals(errorMapWithMsg("Group mapping [druidGroupMapping] does not exist."), response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateDeleteRole()
|
public void testCreateDeleteRole()
|
||||||
{
|
{
|
||||||
|
@ -298,7 +426,7 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRoleAssignment()
|
public void testUserRoleAssignment()
|
||||||
{
|
{
|
||||||
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
@ -339,6 +467,48 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
Assert.assertEquals(expectedRole, response.getEntity());
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGroupMappingRoleAssignment()
|
||||||
|
{
|
||||||
|
Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMapping expectedGroupMapping = new BasicAuthorizerGroupMapping(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"", ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedGroupMapping = new BasicAuthorizerGroupMapping(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"", ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedRole, response.getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAssignedRole()
|
public void testDeleteAssignedRole()
|
||||||
{
|
{
|
||||||
|
@ -357,6 +527,18 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole");
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", new BasicAuthorizerGroupMapping("druid2GroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null);
|
response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
BasicAuthorizerUser expectedUser = new BasicAuthorizerUser(
|
BasicAuthorizerUser expectedUser = new BasicAuthorizerUser(
|
||||||
|
@ -373,6 +555,22 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
);
|
);
|
||||||
Assert.assertEquals(expectedUser2, response.getEntity());
|
Assert.assertEquals(expectedUser2, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerGroupMapping expectedGroupMapping = new BasicAuthorizerGroupMapping(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"", ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
BasicAuthorizerGroupMapping expectedGroupMapping2 = new BasicAuthorizerGroupMapping(
|
||||||
|
"druid2GroupMapping",
|
||||||
|
"", ImmutableSet.of("druidRole")
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping2, response.getEntity());
|
||||||
|
|
||||||
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null);
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null);
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of());
|
||||||
|
@ -396,6 +594,22 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
ImmutableSet.of()
|
ImmutableSet.of()
|
||||||
);
|
);
|
||||||
Assert.assertEquals(expectedUser2, response.getEntity());
|
Assert.assertEquals(expectedUser2, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedGroupMapping = new BasicAuthorizerGroupMapping(
|
||||||
|
"druidGroupMapping",
|
||||||
|
"", ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping, response.getEntity());
|
||||||
|
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", null);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
expectedGroupMapping2 = new BasicAuthorizerGroupMapping(
|
||||||
|
"druid2GroupMapping",
|
||||||
|
"", ImmutableSet.of()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(expectedGroupMapping2, response.getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -446,7 +660,7 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUsersRolesAndPerms()
|
public void testUsersGroupMappingsRolesAndPerms()
|
||||||
{
|
{
|
||||||
Response response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
Response response = resource.createUser(req, AUTHORIZER_NAME, "druid");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
@ -454,6 +668,12 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response = resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
response = resource.createUser(req, AUTHORIZER_NAME, "druid2");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", new BasicAuthorizerGroupMapping("druidGroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.createGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", new BasicAuthorizerGroupMapping("druid2GroupMapping", "", new HashSet<>()));
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
response = resource.createRole(req, AUTHORIZER_NAME, "druidRole");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
@ -490,6 +710,18 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.assignRoleToGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms));
|
BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms));
|
||||||
BasicAuthorizerRole expectedRole2 = new BasicAuthorizerRole("druidRole2", BasicAuthorizerPermission.makePermissionList(perms2));
|
BasicAuthorizerRole expectedRole2 = new BasicAuthorizerRole("druidRole2", BasicAuthorizerPermission.makePermissionList(perms2));
|
||||||
Set<BasicAuthorizerRole> expectedRoles = Sets.newHashSet(expectedRole, expectedRole2);
|
Set<BasicAuthorizerRole> expectedRoles = Sets.newHashSet(expectedRole, expectedRole2);
|
||||||
|
@ -520,10 +752,22 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(expectedUserFullSimplifiedPermissions2, response.getEntity());
|
Assert.assertEquals(expectedUserFullSimplifiedPermissions2, response.getEntity());
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMappingFull expectedGroupMappingFull = new BasicAuthorizerGroupMappingFull("druidGroupMapping", "", expectedRoles);
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedGroupMappingFull, response.getEntity());
|
||||||
|
|
||||||
|
BasicAuthorizerGroupMappingFull expectedGroupMappingFull2 = new BasicAuthorizerGroupMappingFull("druid2GroupMapping", "", expectedRoles);
|
||||||
|
response = resource.getGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", "");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(expectedGroupMappingFull2, response.getEntity());
|
||||||
|
|
||||||
Set<String> expectedUserSet = Sets.newHashSet("druid", "druid2");
|
Set<String> expectedUserSet = Sets.newHashSet("druid", "druid2");
|
||||||
|
Set<String> expectedGroupMappingSet = Sets.newHashSet("druidGroupMapping", "druid2GroupMapping");
|
||||||
BasicAuthorizerRoleFull expectedRoleFull = new BasicAuthorizerRoleFull(
|
BasicAuthorizerRoleFull expectedRoleFull = new BasicAuthorizerRoleFull(
|
||||||
"druidRole",
|
"druidRole",
|
||||||
expectedUserSet,
|
expectedUserSet,
|
||||||
|
expectedGroupMappingSet,
|
||||||
BasicAuthorizerPermission.makePermissionList(perms)
|
BasicAuthorizerPermission.makePermissionList(perms)
|
||||||
);
|
);
|
||||||
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", null);
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", null);
|
||||||
|
@ -549,6 +793,7 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
BasicAuthorizerRoleFull expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
BasicAuthorizerRoleFull expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
||||||
"druidRole2",
|
"druidRole2",
|
||||||
expectedUserSet,
|
expectedUserSet,
|
||||||
|
expectedGroupMappingSet,
|
||||||
BasicAuthorizerPermission.makePermissionList(perms2)
|
BasicAuthorizerPermission.makePermissionList(perms2)
|
||||||
);
|
);
|
||||||
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", null);
|
response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", null);
|
||||||
|
@ -620,16 +865,24 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid2", "druidRole2");
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromGroupMapping(req, AUTHORIZER_NAME, "druidGroupMapping", "druidRole");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
response = resource.unassignRoleFromGroupMapping(req, AUTHORIZER_NAME, "druid2GroupMapping", "druidRole2");
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
expectedUserFull = new BasicAuthorizerUserFull("druid", Sets.newHashSet(expectedRole2));
|
expectedUserFull = new BasicAuthorizerUserFull("druid", Sets.newHashSet(expectedRole2));
|
||||||
expectedUserFull2 = new BasicAuthorizerUserFull("druid2", Sets.newHashSet(expectedRole));
|
expectedUserFull2 = new BasicAuthorizerUserFull("druid2", Sets.newHashSet(expectedRole));
|
||||||
expectedRoleFull = new BasicAuthorizerRoleFull(
|
expectedRoleFull = new BasicAuthorizerRoleFull(
|
||||||
"druidRole",
|
"druidRole",
|
||||||
Sets.newHashSet("druid2"),
|
Sets.newHashSet("druid2"),
|
||||||
|
Sets.newHashSet("druid2GroupMapping"),
|
||||||
BasicAuthorizerPermission.makePermissionList(perms)
|
BasicAuthorizerPermission.makePermissionList(perms)
|
||||||
);
|
);
|
||||||
expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
expectedRoleFull2 = new BasicAuthorizerRoleFull(
|
||||||
"druidRole2",
|
"druidRole2",
|
||||||
Sets.newHashSet("druid"),
|
Sets.newHashSet("druid"),
|
||||||
|
Sets.newHashSet("druidGroupMapping"),
|
||||||
BasicAuthorizerPermission.makePermissionList(perms2)
|
BasicAuthorizerPermission.makePermissionList(perms2)
|
||||||
);
|
);
|
||||||
expectedUserFullSimplifiedPermissions = new BasicAuthorizerUserFullSimplifiedPermissions(
|
expectedUserFullSimplifiedPermissions = new BasicAuthorizerUserFullSimplifiedPermissions(
|
||||||
|
@ -705,15 +958,10 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
final int innerI = i;
|
final int innerI = i;
|
||||||
String roleName = "druidRole-" + i;
|
String roleName = "druidRole-" + i;
|
||||||
addRoleCallables.add(
|
addRoleCallables.add(
|
||||||
new Callable<Void>()
|
() -> {
|
||||||
{
|
Response response12 = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", roleName);
|
||||||
@Override
|
responseCodesAssign[innerI] = response12.getStatus();
|
||||||
public Void call() throws Exception
|
return null;
|
||||||
{
|
|
||||||
Response response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", roleName);
|
|
||||||
responseCodesAssign[innerI] = response.getStatus();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -748,15 +996,10 @@ public class CoordinatorBasicAuthorizerResourceTest
|
||||||
final int innerI = i;
|
final int innerI = i;
|
||||||
String roleName = "druidRole-" + i;
|
String roleName = "druidRole-" + i;
|
||||||
removeRoleCallables.add(
|
removeRoleCallables.add(
|
||||||
new Callable<Void>()
|
() -> {
|
||||||
{
|
Response response1 = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", roleName);
|
||||||
@Override
|
responseCodesRemove[innerI] = response1.getStatus();
|
||||||
public Void call() throws Exception
|
return null;
|
||||||
{
|
|
||||||
Response response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", roleName);
|
|
||||||
responseCodesRemove[innerI] = response.getStatus();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,13 @@ import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCac
|
||||||
public class NoopBasicAuthorizerCacheNotifier implements BasicAuthorizerCacheNotifier
|
public class NoopBasicAuthorizerCacheNotifier implements BasicAuthorizerCacheNotifier
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void addUpdate(String authorizerPrefix, byte[] userAndRoleMap)
|
public void addUpdateUser(String authorizerPrefix, byte[] userAndRoleMap)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addUpdateGroupMapping(String authorizerPrefix, byte[] groupMappingAndRoleMap)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -467,7 +467,7 @@ public class ITBasicAuthConfigurationTest
|
||||||
|
|
||||||
LOG.info("Testing Avatica query on broker with incorrect credentials.");
|
LOG.info("Testing Avatica query on broker with incorrect credentials.");
|
||||||
testAvaticaAuthFailure(brokerUrl);
|
testAvaticaAuthFailure(brokerUrl);
|
||||||
|
|
||||||
LOG.info("Testing Avatica query on router with incorrect credentials.");
|
LOG.info("Testing Avatica query on router with incorrect credentials.");
|
||||||
testAvaticaAuthFailure(routerUrl);
|
testAvaticaAuthFailure(routerUrl);
|
||||||
|
|
||||||
|
@ -526,7 +526,7 @@ public class ITBasicAuthConfigurationTest
|
||||||
catch (AvaticaSqlException ase) {
|
catch (AvaticaSqlException ase) {
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
ase.getErrorMessage(),
|
ase.getErrorMessage(),
|
||||||
"Error while executing SQL \"SELECT * FROM INFORMATION_SCHEMA.COLUMNS\": Remote driver error: ForbiddenException: Authentication failed."
|
"Error while executing SQL \"SELECT * FROM INFORMATION_SCHEMA.COLUMNS\": Remote driver error: BasicSecurityAuthenticationException: User metadata store authentication failed username[admin]."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -569,6 +569,18 @@ authorizerName
|
||||||
druid_system
|
druid_system
|
||||||
pollingPeriod
|
pollingPeriod
|
||||||
roleName
|
roleName
|
||||||
|
LDAP
|
||||||
|
ldap
|
||||||
|
MyBasicMetadataAuthenticator
|
||||||
|
MyBasicLDAPAuthenticator
|
||||||
|
MyBasicMetadataAuthorizer
|
||||||
|
MyBasicLDAPAuthorizer
|
||||||
|
credentialsValidator
|
||||||
|
sAMAccountName
|
||||||
|
objectClass
|
||||||
|
initialAdminRole
|
||||||
|
adminGroupMapping
|
||||||
|
groupMappingName
|
||||||
- ../docs/development/extensions-core/druid-kerberos.md
|
- ../docs/development/extensions-core/druid-kerberos.md
|
||||||
8Kb
|
8Kb
|
||||||
HttpComponents
|
HttpComponents
|
||||||
|
|
Loading…
Reference in New Issue