Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/python/cli/command/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def options_get(key, f=lambda x: x):
remove_properties=[]
if remove_properties is None
else remove_properties,
new_client_id=options_get(Arguments.NEW_CLIENT_ID),
new_client_secret=options_get(Arguments.NEW_CLIENT_SECRET),
)
elif options.command == Commands.PRINCIPAL_ROLES:
from cli.command.principal_roles import PrincipalRolesCommand
Expand Down
19 changes: 19 additions & 0 deletions client/python/cli/command/principals.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Principal,
PrincipalWithCredentials,
UpdatePrincipalRequest,
ResetPrincipalRequest
)


Expand All @@ -55,6 +56,8 @@ class PrincipalsCommand(Command):
properties: Optional[Dict[str, StrictStr]]
set_properties: Dict[str, StrictStr]
remove_properties: List[str]
new_client_id: Optional[str] = None
new_client_secret: Optional[str] = None

def _get_catalogs(self, api: PolarisDefaultApi):
for catalog in api.list_catalogs().catalogs:
Expand Down Expand Up @@ -171,5 +174,21 @@ def execute(self, api: PolarisDefaultApi) -> None:
role_data["catalog_roles"].append(catalog_data)
result["principal_roles"].append(role_data)
print(json.dumps(result))
elif self.principals_subcommand == Subcommands.RESET:
if self.new_client_id or self.new_client_secret:
request = ResetPrincipalRequest(
clientId=self.new_client_id, clientSecret=self.new_client_secret
)
print(
self.build_credential_json(
api.reset_credentials(self.principal_name, request)
)
)
else:
print(
self.build_credential_json(
api.reset_credentials(self.principal_name, None)
)
)
else:
raise Exception(f"{self.principals_subcommand} is not supported in the CLI")
7 changes: 7 additions & 0 deletions client/python/cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class Subcommands:
GRANT = "grant"
REVOKE = "revoke"
ACCESS = "access"
RESET = "reset"


class Actions:
Expand Down Expand Up @@ -155,6 +156,8 @@ class Arguments:
VIEW = "view"
CASCADE = "cascade"
CLIENT_SECRET = "client_secret"
NEW_CLIENT_ID = "new_client_id"
NEW_CLIENT_SECRET = "new_client_secret"
ACCESS_TOKEN = "access_token"
HOST = "host"
PORT = "port"
Expand Down Expand Up @@ -317,6 +320,10 @@ class Create:
class Revoke:
PRINCIPAL_ROLE = "A principal role to revoke from this principal"

class Reset:
CLIENT_ID = "The new client ID for the principal"
CLIENT_SECRET = "The new client secret for the principal"

class PrincipalRoles:
PRINCIPAL_ROLE = "The name of a principal role"
LIST = (
Expand Down
4 changes: 4 additions & 0 deletions client/python/cli/options/option_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ def get_tree() -> List[Option]:
Argument(Arguments.REMOVE_PROPERTY, str, Hints.REMOVE_PROPERTY, allow_repeats=True),
], input_name=Arguments.PRINCIPAL),
Option(Subcommands.ACCESS, input_name=Arguments.PRINCIPAL),
Option(Subcommands.RESET, args=[
Argument(Arguments.NEW_CLIENT_ID, str, Hints.Principals.Reset.CLIENT_ID),
Argument(Arguments.NEW_CLIENT_SECRET, str, Hints.Principals.Reset.CLIENT_SECRET),
], input_name=Arguments.PRINCIPAL),
]),
Option(Commands.PRINCIPAL_ROLES, 'manage principal roles', children=[
Option(Subcommands.CREATE, args=[
Expand Down
2 changes: 2 additions & 0 deletions client/python/integration_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#
# 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
Expand All @@ -14,6 +15,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

import codecs
import os
Expand Down
2 changes: 2 additions & 0 deletions client/python/integration_tests/test_catalog_apis.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#
# 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
Expand All @@ -14,6 +15,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

import os.path
import time
Expand Down
102 changes: 102 additions & 0 deletions client/python/integration_tests/test_management_apis.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#
# 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
Expand All @@ -14,6 +15,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

from integration_tests.conftest import (
create_principal,
create_principal_role,
Expand All @@ -28,6 +31,7 @@
RevokeGrantRequest,
PolarisDefaultApi,
Catalog,
ResetPrincipalRequest,
)


Expand Down Expand Up @@ -125,3 +129,101 @@ def test_grants(management_client: PolarisDefaultApi, test_catalog: Catalog) ->
assert len(grants.grants) == 0
finally:
management_client.delete_catalog_role(test_catalog.name, catalog_role.name)


def test_reset_principal_credentials_default(
management_client: PolarisDefaultApi,
) -> None:
principal_name = "test_principal_for_reset_creds_default"
principal_with_creds = create_principal(management_client, principal_name)
initial_client_id = principal_with_creds.principal.client_id
initial_client_secret = (
principal_with_creds.credentials.client_secret.get_secret_value()
)
try:
reset_request = ResetPrincipalRequest()
new_principal_with_creds = management_client.reset_credentials(
principal_name=principal_name, reset_principal_request=reset_request
)
current_client_id = new_principal_with_creds.principal.client_id
current_client_secret = (
new_principal_with_creds.credentials.client_secret.get_secret_value()
)

assert initial_client_id == current_client_id
assert initial_client_secret != current_client_secret
finally:
management_client.delete_principal(principal_name=principal_name)


def test_reset_principal_credentials_custom(
management_client: PolarisDefaultApi,
) -> None:
principal_name = "test_principal_for_reset_creds_custom"
create_principal(management_client, principal_name)
custom_client_id = "e469c048cf866df1"
custom_client_secret = "1f37adcd21bf1586ed090332eded9cd3"
try:
reset_request = ResetPrincipalRequest(
clientId=custom_client_id, clientSecret=custom_client_secret
)
new_principal_with_creds = management_client.reset_credentials(
principal_name=principal_name, reset_principal_request=reset_request
)
current_client_id = new_principal_with_creds.principal.client_id
current_client_secret = (
new_principal_with_creds.credentials.client_secret.get_secret_value()
)

assert current_client_id == custom_client_id
assert current_client_secret == custom_client_secret
finally:
management_client.delete_principal(principal_name=principal_name)


def test_reset_principal_credentials_custom_client_id(
management_client: PolarisDefaultApi,
) -> None:
principal_name = "test_principal_for_reset_creds_client_id"
principal_with_creds = create_principal(management_client, principal_name)
initial_client_secret = (
principal_with_creds.credentials.client_secret.get_secret_value()
)
custom_client_id = "e469c048cf866df1"
try:
reset_request = ResetPrincipalRequest(clientId=custom_client_id)
new_principal_with_creds = management_client.reset_credentials(
principal_name=principal_name, reset_principal_request=reset_request
)
current_client_id = new_principal_with_creds.principal.client_id
current_client_secret = (
new_principal_with_creds.credentials.client_secret.get_secret_value()
)

assert current_client_id == custom_client_id
assert initial_client_secret != current_client_secret
finally:
management_client.delete_principal(principal_name=principal_name)


def test_reset_principal_credentials_custom_client_secret(
management_client: PolarisDefaultApi,
) -> None:
principal_name = "test_principal_for_reset_creds_client_secret"
principal_with_creds = create_principal(management_client, principal_name)
initial_client_id = principal_with_creds.principal.client_id
custom_client_secret = "1f37adcd21bf1586ed090332eded9cd3"
try:
reset_request = ResetPrincipalRequest(clientSecret=custom_client_secret)
new_principal_with_creds = management_client.reset_credentials(
principal_name=principal_name, reset_principal_request=reset_request
)
current_client_id = new_principal_with_creds.principal.client_id
current_client_secret = (
new_principal_with_creds.credentials.client_secret.get_secret_value()
)

assert initial_client_id == current_client_id
assert current_client_secret == custom_client_secret
finally:
management_client.delete_principal(principal_name=principal_name)
34 changes: 32 additions & 2 deletions client/python/test/test_cli_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ def capture_method(method_name):
def _capture(*args, **kwargs):
client.call_tracker['_method'] = method_name
for i, arg in enumerate(args):
if arg is not None:
client.call_tracker[i] = arg
client.call_tracker[i] = arg

return _capture

Expand Down Expand Up @@ -588,6 +587,37 @@ def get(obj, arg_string):
(0, 'catalog.connection_config_info.uri'): 'u',
})

check_arguments(
mock_execute(['principals', 'reset', 'test', '--new-client-id', 'e469c048cf866df1', '--new-client-secret', 'e469c048cf866dfae469c048cf866df1']),
'reset_credentials', {
(0, None): 'test',
(1, 'client_id'): 'e469c048cf866df1',
(1, 'client_secret'): 'e469c048cf866dfae469c048cf866df1',
})

check_arguments(
mock_execute(['principals', 'reset', 'test']),
'reset_credentials', {
(0, None): 'test',
(1, None): None,
})

check_arguments(
mock_execute(['principals', 'reset', 'test', '--new-client-id', 'e469c048cf866df1']),
'reset_credentials', {
(0, None): 'test',
(1, 'client_id'): 'e469c048cf866df1',
(1, 'client_secret'): None,
})

check_arguments(
mock_execute(['principals', 'reset', 'test', '--new-client-secret', 'e469c048cf866dfae469c048cf866df1']),
'reset_credentials', {
(0, None): 'test',
(1, 'client_id'): None,
(1, 'client_secret'): 'e469c048cf866dfae469c048cf866df1',
})


if __name__ == '__main__':
unittest.main()
27 changes: 27 additions & 0 deletions site/content/in-dev/unreleased/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ The `principals` command is used to manage principals within Polaris.
5. rotate-credentials
6. update
7. access
8. reset

#### create

Expand Down Expand Up @@ -418,6 +419,32 @@ options:
polaris principals access quickstart_user
```

#### reset

The `reset` subcommand is used to reset principal credentials.

```
input: polaris principals reset --help
options:
reset
Named arguments:
--new-client-id The new client ID for the principal
--new-client-secret The new client secret for the principal
Positional arguments:
principal
```

##### Examples

```
polaris principals create some_user

polaris principals reset some_user
polaris principals reset --new-client-id ${NEW_CLIENT_ID} some_user
polaris principals reset --new-client-secret ${NEW_CLIENT_SECRET} some_user
polaris principals reset --new-client-id ${NEW_CLIENT_ID} --new-client-secret ${NEW_CLIENT_SECRET} some_user
```

### Principal Roles

The `principal-roles` command is used to create, discover, and manage principal roles within Polaris. Additionally, this command can identify principals or catalog roles associated with a principal role, and can be used to grant a principal role to a principal.
Expand Down