abhishekrb19 commented on code in PR #14009:
URL: https://github.com/apache/druid/pull/14009#discussion_r1160797075


##########
docs/operations/security-overview.md:
##########
@@ -129,24 +122,23 @@ Within Druid's operating context, authenticators control 
the way user identities
 
 The following graphic depicts the course of request through the authentication 
process:
 
-
-![Druid security check flow](../assets/security-model-1.png "Druid security 
check flow") 
-
+![Druid security check flow](../assets/security-model-1.png "Druid security 
check flow")
 
 ## Enable an authenticator
 
-To authenticate requests in Druid, you configure an Authenticator. 
Authenticator extensions exist for HTTP basic authentication, LDAP, and 
Kerberos.  
+To authenticate requests in Druid, you configure an Authenticator. 
Authenticator extensions exist for HTTP basic authentication, LDAP, and 
Kerberos.
 
-The following takes you through sample configuration steps for enabling basic 
auth:  
+The following takes you through sample configuration steps for enabling basic 
auth:
 
 1. Add the `druid-basic-security` extension to `druid.extensions.loadList` in 
`common.runtime.properties`. For the quickstart installation, for example, the 
properties file is at `conf/druid/cluster/_common`:
    ```
    druid.extensions.loadList=["druid-basic-security", "druid-histogram", 
"druid-datasketches", "druid-kafka-indexing-service"]
    ```
-2. Configure the basic Authenticator, Authorizer, and Escalator settings in 
the same common.runtime.properties file. The Escalator defines how Druid 
processes authenticate with one another. 
+2. Configure the basic Authenticator, Authorizer, and Escalator settings in 
the same common.runtime.properties file. The Escalator defines how Druid 
processes authenticate with one another.
 
-An example configuration:
-   ```
+   An example configuration:
+
+   ```text

Review Comment:
   nit: if you switch this from `text` -> `properties`, you get some nice 
syntax highlighting
   
   ```suggestion
      ```properties



##########
docs/operations/security-overview.md:
##########
@@ -176,70 +170,83 @@ An example configuration:
    druid.auth.authorizer.MyBasicMetadataAuthorizer.type=basic
    ```
 
-3. Restart the cluster. 
+3. Restart the cluster.
 
-See [Authentication and Authorization](../design/auth.md) for more information 
about the Authenticator, Escalator, and Authorizer concepts. See [Basic 
Security](../development/extensions-core/druid-basic-security.md) for more 
information about the extension used in the examples above, and 
[Kerberos](../development/extensions-core/druid-kerberos.md) for Kerberos 
authentication.
+See the following topics for more information:
 
+* [Authentication and Authorization](../design/auth.md) for more information 
about the Authenticator,
+Escalator, and Authorizer.
+* [Basic Security](../development/extensions-core/druid-basic-security.md) for 
more information about
+the extension used in the examples above.
+* [Kerberos](../development/extensions-core/druid-kerberos.md) for Kerberos 
authentication.
+* [User authentication and authorization](security-user-auth.md) for details 
about permissions.
+* [SQL permissions](security-user-auth.md#sql-permissions) for permissions on 
SQL system tables.
+* [The `druidapi` Python library](../tutorials/tutorial-jupyter-index.md),
+  provided as part of the Druid tutorials, for functions you can
+  use in a Jupyter notebook to learn how security works, and to set up users 
and roles for testing.
 
 ## Enable authorizers
 
-After enabling the basic auth extension, you can add users, roles, and 
permissions via the Druid Coordinator `user` endpoint. Note that you cannot 
assign permissions directly to individual users. They must be assigned through 
roles. 
+After enabling the basic auth extension, you can add users, roles, and 
permissions via the Druid Coordinator `user` endpoint. Note that you cannot 
assign permissions directly to individual users. They must be assigned through 
roles.
 
 The following diagram depicts the authorization model, and the relationship 
between users, roles, permissions, and resources.
- 
-![Druid Security model](../assets/security-model-2.png "Druid security model") 
 
+![Druid Security model](../assets/security-model-2.png "Druid security model")
 
-The following steps walk through a sample setup procedure:  
+
+The following steps walk through a sample setup procedure:
 
 > The default Coordinator API port is 8081 for non-TLS connections and 8281 
 > for secured connections.
 
-1. Create a user by issuing a POST request to 
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>`,
 replacing USERNAME with the *new* username you are trying to create. For 
example: 
-  ```
+1. Create a user by issuing a POST request to 
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>`,
+   replacing USERNAME with the *new* username you are trying to create. For 
example:
+   ```bash
    curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname
-  ```
-  >  If you have TLS enabled, be sure to adjust the curl command accordingly. 
For example, if your Druid servers use self-signed certificates, you may choose 
to include the `insecure` curl option to forgo certificate checking for the 
curl command. 
+   ```
+   >  If you have TLS enabled, be sure to adjust the curl command accordingly. 
For example, if your Druid servers use self-signed certificates,
+   you may choose to include the `insecure` curl option to forgo certificate 
checking for the curl command.
+
 2. Add a credential for the user by issuing a POST to 
`druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/<USERNAME>/credentials`.
 For example:
-    ```
-    curl -u admin:password1 -H'Content-Type: application/json' -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname/credentials
 --data-raw '{"password": "my_password"}'
-    ```
-2. For each authenticator user you create, create a corresponding authorizer 
user by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>`.
 For example: 
-       ```
-       curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname
-       ```
-3. Create authorizer roles to control permissions by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>`.
 For example: 
-       ```
+   ```bash
+   curl -u admin:password1 -H'Content-Type: application/json' -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authentication/db/MyBasicMetadataAuthenticator/users/myname/credentials
 --data-raw '{"password": "my_password"}'
+   ```
+2. For each authenticator user you create, create a corresponding authorizer 
user by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>`.
 For example:
+        ```bash
+        curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname
+        ```
+3. Create authorizer roles to control permissions by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>`.
 For example:
+        ```bash
    curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole
    ```
-4. Assign roles to users by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>/roles/<ROLENAME>`.
 For example: 
-       ```
-       curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname/roles/myrole
 | jq
-       ```
-5. Finally, attach permissions to the roles to control how they can interact 
with Druid at 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>/permissions`.
 
-       For example: 
-       ```
-       curl -u admin:password1 -H'Content-Type: application/json' -XPOST 
--data-binary @perms.json 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole/permissions
-       ```
+4. Assign roles to users by issuing a POST request to 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/<USERNAME>/roles/<ROLENAME>`.
 For example:
+        ```bash
+        curl -u admin:password1 -XPOST 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/users/myname/roles/myrole
 | jq
+        ```
+5. Finally, attach permissions to the roles to control how they can interact 
with Druid at 
`druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/<ROLENAME>/permissions`.
+        For example:
+        ```bash
+        curl -u admin:password1 -H'Content-Type: application/json' -XPOST 
--data-binary @perms.json 
https://my-coordinator-ip:8281/druid-ext/basic-security/authorization/db/MyBasicMetadataAuthorizer/roles/myrole/permissions
+        ```
        The payload of `perms.json` should be in the form:
-       ```
-    [
+       ```json
+  [
     {
       "resource": {
-        "name": "<PATTERN>",
-        "type": "DATASOURCE"
+        "type": "DATASOURCE",
+        "name": "<PATTERN>"
       },
       "action": "READ"
     },
     {
       "resource": {
-      "name": "STATE",
-      "type": "STATE"
-    },
-    "action": "READ"
+        "type": "STATE",
+        "name": "STATE"
+      },
+      "action": "READ"
     }
-    ]
-    ```
-    > Note: Druid treats the resource name as a regular expression (regex). 
You can use a specific datasource name or regex to grant permissions for 
multiple datasources at a time.
+  ]
+  ```

Review Comment:
   I suspect there's a missing backtick in this diff that's causing markdown to 
render the following section inside a codeblock - please see screenshot:
   <img width="1190" alt="CleanShot 2023-04-07 at 09 19 12@2x" 
src="https://user-images.githubusercontent.com/8687261/230642632-2cb849a2-e56d-4e8f-8cc5-26198376c04f.png";>
   



##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+    '''
+    Manage Basic security. The Druid session must be logged in with the super
+    user, or some other user who has permission to modify user credentials.
+
+    Each client works with one authorizer/authenticator pair. Create multiple 
clients if you have to
+    work with multiple authenticators on a single server.
+
+    The basic pattern to add users and permissions is:
+
+    ```
+    # Create a client for your coordinator (Basic auth is not proxied through 
the router)
+    coord = druidapi.jupyter_client('http://localhost:8081', auth=('admin', 
'password'))
+
+    # Get a client for your authenticator and authorizer:
+    ac = coord.basic_security('yourAuthorizer', 'yourAuthenticator')
+
+    # Create a user in both the authenticator and authorizer
+    ac.add_user('bob', 'secret')
+
+    # Define a role
+    ac.add_role('myRole')
+
+    # Assign the role to the user
+    ac.assign_role_to_user('myRole', 'bob')
+
+    # Give the role some permissions
+    ac.grant_permissions('myRole', [[consts.DATASOURCE_RESOURCE, 'foo', 
consts.READ_ACTION]])
+    ```
+
+    Then use the various other methods to list users, roles and permissions to 
verify the
+    setup. You can then create a second Druid client that acts as the new user:
+
+    ```
+    bob_client = druidapi.jupyter_client('http://localhost:8888', auth=('bob', 
'secret'))
+    ```
+
+    See 
https://druid.apache.org/docs/latest/operations/security-overview.html#enable-authorizers
+    '''
+
+    def __init__(self, rest_client, authenticator, authorizer=None):
+        self.rest_client = rest_client
+        self.authenticator = authenticator
+        self.authorizer = authorizer if authorizer else authenticator
+
+    # Authentication
+
+    def authentication_status(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_LOAD_STATUS)
+
+    def authentication_refresh(self) -> None:
+        self.rest_client.get(REQ_AUTHENTICATION_REFRESH_ALL)
+
+    def create_authentication_user(self, user) -> None:
+        self.rest_client.post(REQ_AUTHENTICATION_USER, None, 
args=[self.authenticator, user])
+
+    def set_password(self, user, password) -> None:
+        self.rest_client.post_only_json(REQ_AUTHENTICATION_CREDENTIALS, 
{'password': password}, args=[self.authenticator, user])
+
+    def drop_authentication_user(self, user) -> None:
+        self.rest_client.delete(REQ_AUTHENTICATION_USER, 
args=[self.authenticator, user])
+    
+    def authentication_user(self, user) -> dict:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_USER, 
args=[self.authenticator, user])
+    
+    def authentication_users(self) -> list:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_USERS, 
args=[self.authenticator])
+    
+    # Authorization
+    # Groups are not documented. Use at your own risk.
+
+    def authorization_status(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_LOAD_STATUS)
+
+    def authorization_refresh(self) -> None:
+        self.rest_client.get(REQ_AUTHORIZATION_REFRESH_ALL)
+
+    def create_authorization_user(self, user) -> None:
+        self.rest_client.post(REQ_AUTHORIZATION_USER, None, 
args=[self.authorizer, user])
+
+    def drop_authorization_user(self, user) -> None:
+        self.rest_client.delete(REQ_AUTHORIZATION_USER, 
args=[self.authenticator, user])
+    
+    def authorization_user(self, user) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_USER, 
args=[self.authorizer, user])
+    
+    def authorization_users(self) -> list:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_USERS, 
args=[self.authorizer])
+    
+    def create_group(self, group, payload):
+        self.rest_client.post_json(REQ_AUTHORIZATION_GROUP_MAPPING, payload, 
args=[self.authorizer, group])
+
+    def drop_group(self, group):
+        self.rest_client.delete(REQ_AUTHORIZATION_GROUP_MAPPING, 
args=[self.authorizer, group])
+
+    def groups(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPINGS, 
args=[self.authorizer])
+
+    def group(self, group) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPING, 
args=[self.authorizer, group])
+    
+    def roles(self):
+        return self.rest_client.get_json(REQ_AUTHORIZATION_ROLES, 
args=[self.authenticator])
+ 
+    def add_role(self, role):
+        self.rest_client.post(REQ_AUTHORIZATION_ROLE, None, 
args=[self.authenticator, role])
+   
+    def drop_role(self, role):
+        self.rest_client.delete(REQ_AUTHORIZATION_ROLE, args=[self.authorizer, 
role])
+
+    def set_role_permissions(self, role, permissions):
+        self.rest_client.post_only_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS, 
permissions, args=[self.authenticator, role])
+
+    def role_permissions(self, role):
+        return self.rest_client.get_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS, 
args=[self.authenticator, role])
+    
+    def assign_role_to_user(self, role, user):
+        self.rest_client.post(REQ_AUTHORIZATION_USER_ROLE, None, 
args=[self.authenticator, user, role])
+
+    def revoke_role_from_user(self, role, user):
+        self.rest_client.delete(REQ_AUTHORIZATION_USER_ROLE, 
args=[self.authenticator, user, role])
+
+    def assign_role_to_group(self, group, role):
+        self.rest_client.post(REQ_AUTHORIZATION_GROUP_ROLE, None, 
args=[self.authenticator, group, role])
+
+    def revoke_role_from_group(self, group, role):
+        self.rest_client.delete(REQ_AUTHORIZATION_GROUP_ROLE, 
args=[self.authenticator, group, role])
+
+    def user_map(self):
+        # Result uses Smile encoding, not JSON. This is really just for sanity
+        # checks: a Python client can't make use of the info.
+        # To decode, see newsmile: https://pypi.org/project/newsmile/
+        # However, the format Druid returns is not quite compatible with 
newsmile
+        return self.rest_client.get(REQ_USER_MAP, args=[self.authenticator])
+
+    # Convenience methods
+
+    def add_user(self, user, password):
+        '''
+        Adds a user to both the authenticator and authorizer.
+        '''
+        self.create_authentication_user(user)
+        self.set_password(user, password)
+        self.create_authorization_user(user)
+
+    def drop_user(self, user):
+        '''
+        Drops a user from both the authenticator and authorizer.
+        '''
+        self.drop_authorization_user(user)
+        self.drop_authentication_user(user)
+
+    def users(self):
+        '''
+        Returns the list of authenticator and authorizer users.
+        '''
+        return {
+            "authenticator": self.authorization_users(),
+            "authorizer": self.authentication_users()
+        }
+
+    def status(self):
+        '''
+        Returns both the authenticator and authorizer status.
+        '''
+        return {
+            "authenticator": self.authorization_status(),

Review Comment:
   Same as above - I think the status assignment for authenticator and 
authorizer is flipped.



##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+    '''
+    Manage Basic security. The Druid session must be logged in with the super

Review Comment:
   nit: "Basic" -> "basic"



##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/druid.py:
##########
@@ -116,6 +117,33 @@ def datasources(self) -> DatasourceClient:
         if not self.datasource_client:
             self.datasource_client = DatasourceClient(self.rest_client)
         return self.datasource_client
+    
+    def basic_security(self, authenticator, authorizer=None):
+        '''
+        Returns a client to work with a basic authorization 
authenticator/authorizer pair.
+        This client assumes the typical case of one authenticator and one 
authorizer. If
+        you have more than one, create multiple clients.
+
+        The basic security API is not proxied through the Router: it must work 
directly with
+        the Coordinator. Create an ad hoc Druid client for your Coordinator. 
Because you have
+        basic security enabled, you must specify the admin user and password:
+
+        ```
+        coord = druidapi.jupyter_client('http://localhost:8081', 
auth=('admin', 'admin-pwd'))

Review Comment:
   Neat!



##########
examples/quickstart/jupyter-notebooks/druidapi/druidapi/basic_auth.py:
##########
@@ -0,0 +1,238 @@
+# 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.
+
+BASIC_AUTH_BASE = '/druid-ext/basic-security'
+
+AUTHENTICATION_BASE = BASIC_AUTH_BASE + '/authentication'
+REQ_AUTHENTICATION_LOAD_STATUS = AUTHENTICATION_BASE + '/loadStatus'
+REQ_AUTHENTICATION_REFRESH_ALL = AUTHENTICATION_BASE + '/refreshAll'
+AUTHENTICATOR_BASE = AUTHENTICATION_BASE + '/db/{}'
+REQ_AUTHENTICATION_USERS = AUTHENTICATOR_BASE + '/users'
+REQ_AUTHENTICATION_USER = REQ_AUTHENTICATION_USERS + '/{}'
+REQ_AUTHENTICATION_CREDENTIALS = REQ_AUTHENTICATION_USER + '/credentials'
+
+AUTHORIZATION_BASE = BASIC_AUTH_BASE + '/authorization'
+REQ_AUTHORIZATION_LOAD_STATUS = AUTHORIZATION_BASE + '/loadStatus'
+REQ_AUTHORIZATION_REFRESH_ALL = AUTHORIZATION_BASE + '/refreshAll'
+AUTHORIZATION_BASE = AUTHORIZATION_BASE + '/db/{}'
+REQ_AUTHORIZATION_USERS = AUTHORIZATION_BASE + '/users'
+REQ_AUTHORIZATION_USER = REQ_AUTHORIZATION_USERS + '/{}'
+REQ_AUTHORIZATION_USER_ROLES = REQ_AUTHORIZATION_USER + '/roles'
+REQ_AUTHORIZATION_USER_ROLE = REQ_AUTHORIZATION_USER_ROLES + '/{}'
+REQ_AUTHORIZATION_GROUP_MAPPINGS = AUTHORIZATION_BASE + '/groupMappings'
+REQ_AUTHORIZATION_GROUP_MAPPING = AUTHORIZATION_BASE + '/groupMappings/{}'
+REQ_AUTHORIZATION_GROUP_ROLES = REQ_AUTHORIZATION_GROUP_MAPPING + '/roles'
+REQ_AUTHORIZATION_GROUP_ROLE = REQ_AUTHORIZATION_GROUP_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLES = AUTHORIZATION_BASE + '/roles'
+REQ_AUTHORIZATION_ROLE = REQ_AUTHORIZATION_ROLES + '/{}'
+REQ_AUTHORIZATION_ROLE_PERMISSIONS = REQ_AUTHORIZATION_ROLE + '/permissions'
+REQ_USER_MAP = AUTHORIZATION_BASE + '/cachedSerializedUserMap'
+
+class BasicAuthClient:
+    '''
+    Manage Basic security. The Druid session must be logged in with the super
+    user, or some other user who has permission to modify user credentials.
+
+    Each client works with one authorizer/authenticator pair. Create multiple 
clients if you have to
+    work with multiple authenticators on a single server.
+
+    The basic pattern to add users and permissions is:
+
+    ```
+    # Create a client for your coordinator (Basic auth is not proxied through 
the router)
+    coord = druidapi.jupyter_client('http://localhost:8081', auth=('admin', 
'password'))
+
+    # Get a client for your authenticator and authorizer:
+    ac = coord.basic_security('yourAuthorizer', 'yourAuthenticator')
+
+    # Create a user in both the authenticator and authorizer
+    ac.add_user('bob', 'secret')
+
+    # Define a role
+    ac.add_role('myRole')
+
+    # Assign the role to the user
+    ac.assign_role_to_user('myRole', 'bob')
+
+    # Give the role some permissions
+    ac.grant_permissions('myRole', [[consts.DATASOURCE_RESOURCE, 'foo', 
consts.READ_ACTION]])
+    ```
+
+    Then use the various other methods to list users, roles and permissions to 
verify the
+    setup. You can then create a second Druid client that acts as the new user:
+
+    ```
+    bob_client = druidapi.jupyter_client('http://localhost:8888', auth=('bob', 
'secret'))
+    ```
+
+    See 
https://druid.apache.org/docs/latest/operations/security-overview.html#enable-authorizers
+    '''
+
+    def __init__(self, rest_client, authenticator, authorizer=None):
+        self.rest_client = rest_client
+        self.authenticator = authenticator
+        self.authorizer = authorizer if authorizer else authenticator
+
+    # Authentication
+
+    def authentication_status(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_LOAD_STATUS)
+
+    def authentication_refresh(self) -> None:
+        self.rest_client.get(REQ_AUTHENTICATION_REFRESH_ALL)
+
+    def create_authentication_user(self, user) -> None:
+        self.rest_client.post(REQ_AUTHENTICATION_USER, None, 
args=[self.authenticator, user])
+
+    def set_password(self, user, password) -> None:
+        self.rest_client.post_only_json(REQ_AUTHENTICATION_CREDENTIALS, 
{'password': password}, args=[self.authenticator, user])
+
+    def drop_authentication_user(self, user) -> None:
+        self.rest_client.delete(REQ_AUTHENTICATION_USER, 
args=[self.authenticator, user])
+    
+    def authentication_user(self, user) -> dict:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_USER, 
args=[self.authenticator, user])
+    
+    def authentication_users(self) -> list:
+        return self.rest_client.get_json(REQ_AUTHENTICATION_USERS, 
args=[self.authenticator])
+    
+    # Authorization
+    # Groups are not documented. Use at your own risk.
+
+    def authorization_status(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_LOAD_STATUS)
+
+    def authorization_refresh(self) -> None:
+        self.rest_client.get(REQ_AUTHORIZATION_REFRESH_ALL)
+
+    def create_authorization_user(self, user) -> None:
+        self.rest_client.post(REQ_AUTHORIZATION_USER, None, 
args=[self.authorizer, user])
+
+    def drop_authorization_user(self, user) -> None:
+        self.rest_client.delete(REQ_AUTHORIZATION_USER, 
args=[self.authenticator, user])
+    
+    def authorization_user(self, user) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_USER, 
args=[self.authorizer, user])
+    
+    def authorization_users(self) -> list:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_USERS, 
args=[self.authorizer])
+    
+    def create_group(self, group, payload):
+        self.rest_client.post_json(REQ_AUTHORIZATION_GROUP_MAPPING, payload, 
args=[self.authorizer, group])
+
+    def drop_group(self, group):
+        self.rest_client.delete(REQ_AUTHORIZATION_GROUP_MAPPING, 
args=[self.authorizer, group])
+
+    def groups(self) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPINGS, 
args=[self.authorizer])
+
+    def group(self, group) -> dict:
+        return self.rest_client.get_json(REQ_AUTHORIZATION_GROUP_MAPPING, 
args=[self.authorizer, group])
+    
+    def roles(self):
+        return self.rest_client.get_json(REQ_AUTHORIZATION_ROLES, 
args=[self.authenticator])
+ 
+    def add_role(self, role):
+        self.rest_client.post(REQ_AUTHORIZATION_ROLE, None, 
args=[self.authenticator, role])
+   
+    def drop_role(self, role):
+        self.rest_client.delete(REQ_AUTHORIZATION_ROLE, args=[self.authorizer, 
role])
+
+    def set_role_permissions(self, role, permissions):
+        self.rest_client.post_only_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS, 
permissions, args=[self.authenticator, role])
+
+    def role_permissions(self, role):
+        return self.rest_client.get_json(REQ_AUTHORIZATION_ROLE_PERMISSIONS, 
args=[self.authenticator, role])
+    
+    def assign_role_to_user(self, role, user):
+        self.rest_client.post(REQ_AUTHORIZATION_USER_ROLE, None, 
args=[self.authenticator, user, role])
+
+    def revoke_role_from_user(self, role, user):
+        self.rest_client.delete(REQ_AUTHORIZATION_USER_ROLE, 
args=[self.authenticator, user, role])
+
+    def assign_role_to_group(self, group, role):
+        self.rest_client.post(REQ_AUTHORIZATION_GROUP_ROLE, None, 
args=[self.authenticator, group, role])
+
+    def revoke_role_from_group(self, group, role):
+        self.rest_client.delete(REQ_AUTHORIZATION_GROUP_ROLE, 
args=[self.authenticator, group, role])
+
+    def user_map(self):
+        # Result uses Smile encoding, not JSON. This is really just for sanity
+        # checks: a Python client can't make use of the info.
+        # To decode, see newsmile: https://pypi.org/project/newsmile/
+        # However, the format Druid returns is not quite compatible with 
newsmile
+        return self.rest_client.get(REQ_USER_MAP, args=[self.authenticator])
+
+    # Convenience methods
+
+    def add_user(self, user, password):
+        '''
+        Adds a user to both the authenticator and authorizer.
+        '''
+        self.create_authentication_user(user)
+        self.set_password(user, password)
+        self.create_authorization_user(user)
+
+    def drop_user(self, user):
+        '''
+        Drops a user from both the authenticator and authorizer.
+        '''
+        self.drop_authorization_user(user)
+        self.drop_authentication_user(user)
+
+    def users(self):
+        '''
+        Returns the list of authenticator and authorizer users.
+        '''
+        return {
+            "authenticator": self.authorization_users(),

Review Comment:
   Should this be:
   ```python
           return {
               "authenticator": self.authentication_users(),
               "authorizer": self. authorization_users()
           }



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to