Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add new az ad app permissions grant and list commands for OAuth2 permissions for AAD registered apps #6975

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@
az ad app update --id e042ec79-34cd-498f-9d9f-123456781234 --set groupMembershipClaims=All

"""
helps['ad app permission grant'] = """
type: command
short-summary: Grant an app OAuth2 permissions from another app
examples:
- name: Grant a native application with OAuth2 permissions from an existing AAD app with TTL of 2 years
text: az ad app permission grant --id e042ec79-34cd-498f-9d9f-1234234 --app-id a0322f79-57df-498f-9d9f-12678 --expires 2
"""
helps['ad app permission list'] = """
type: command
short-summary: List the app OAuth2 permissions
examples:
- name: List the OAuth2 permissions for an existing AAD app
text: az ad app permission list --id e042ec79-34cd-498f-9d9f-1234234
"""
helps['ad user list'] = """
type: command
short-summary: List Azure Active Directory users.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ def load_arguments(self, _):
help="resource scopes and roles the application requires access to. Should be in manifest json format. See examples below for details")
c.argument('native_app', arg_type=get_three_state_flag(), help="an application which can be installed on a user's device or computer")

with self.argument_context('ad app permission grant') as c:
c.argument('app_id', help='clientId of an existing app from which you want to grant permissions to your app')
c.argument('expires', help='Expiry date for the permissions in years, options include 1, 2 or never.')

with self.argument_context('ad app permission list') as c:
c.argument('identifier', options_list=['--id'], help='identifier uri, application id, or object id of the associated application')

with self.argument_context('ad sp') as c:
c.argument('identifier', options_list=['--id'], help='service principal name, or object id')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def load_command_table(self, _):
g.custom_command('delete', 'delete_application')
g.custom_command('list', 'list_apps')
g.custom_show_command('show', 'show_application')
g.custom_command('permission grant', 'grant_application')
g.custom_command('permission list', 'list_granted_application')
g.generic_update_command('update', setter_name='patch_application', setter_type=role_custom,
getter_name='show_application', getter_type=role_custom,
custom_func_name='update_application', custom_func_type=role_custom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,53 @@ def create_application(client, display_name, homepage=None, identifier_uris=None
return result


def list_granted_application(cmd, identifier):
graph_client = _graph_client_factory(cmd.cli_ctx)

# Get the Service Principal ObjectId for the client app
client_sp_object_id = _resolve_service_principal(graph_client.service_principals, appid=identifier)

# Get the OAuth2 permissions client app
permissions = graph_client.oauth2.get(
filter="clientId eq '{}'".format(client_sp_object_id))

return permissions.additional_properties['value']


def grant_application(cmd, identifier, app_id, expires='1'):
graph_client = _graph_client_factory(cmd.cli_ctx)

# Get the Service Principal ObjectId for the client app
client_sp_object_id = _resolve_service_principal(graph_client.service_principals, appid=identifier)

# Get the Service Principal ObjectId for associated app
associated_sp_object_id = _resolve_service_principal(graph_client.service_principals, appid=app_id)

# Build payload
start_date = datetime.datetime.utcnow()
end_date = start_date + relativedelta(years=1)

if expires == '2':
end_date = start_date + relativedelta(years=2)
elif expires.lower() == 'never':
end_date = start_date + relativedelta(years=1000)

payload = {
"odata.type": "Microsoft.DirectoryServices.OAuth2PermissionGrant",
"clientId": client_sp_object_id,
"consentType": "AllPrincipals",
"resourceId": associated_sp_object_id,
"scope": "user_impersonation",
"startTime": start_date.isoformat(),
"expiryTime": end_date.isoformat()
}

# Grant OAuth2 permissions
response = graph_client.oauth2.post(payload)

return response


def update_application(instance, display_name=None, homepage=None, # pylint: disable=unused-argument
identifier_uris=None, password=None, reply_urls=None, key_value=None,
key_type=None, key_usage=None, start_date=None, end_date=None, available_to_other_tenants=None,
Expand Down Expand Up @@ -884,9 +931,13 @@ def delete_service_principal_credential(cmd, identifier, key_id, cert=False):
key_id, identifier))


def _resolve_service_principal(client, identifier):
def _resolve_service_principal(client, identifier=None, appid=None):
Copy link
Contributor

@yugangw-msft yugangw-msft Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please double check this is needed? appid and app id uri are both service principal names which the existing query below should cover it. I did a quick check just now for reference, but let me know if you do need it.

(env) D:\sdk\azure-cli>az ad app show --id e96b494b-61a2-4a42-a33c-e11ab1f1a6b6
{
  "appId": "e96b494b-61a2-4a42-a33c-e11ab1f1a6b6",
    <omit irrelevant fields>
}
(env) D:\sdk\azure-cli>az ad sp show --id e96b494b-61a2-4a42-a33c-e11ab1f1a6b6
{
  "appDisplayName": "yugangw-op2",
  "appId": "e96b494b-61a2-4a42-a33c-e11ab1f1a6b6",
  "appOwnerTenantId": "54826b22-38d6-4fb2-bad9-b7b93a3e9c5a",
   "objectId": "e0a58f57-dcae-48ab-98b6-ee173a6097fa",
  "objectType": "ServicePrincipal",
   servicePrincipalNames": [
    "http://yugangw-op2",
    "e96b494b-61a2-4a42-a33c-e11ab1f1a6b6"
  ],
  "servicePrincipalType": "Application",
   <omit irrelevant fields>
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yugangw-msft I can confirm that the appId can be the same as the objectId, but it is not always. In all of my test cases, like for example creating an app in the portal, the SP associated with the app will be autogenerated and needs to be searched for by the appId. The code is definitely needed.

Copy link
Contributor

@yugangw-msft yugangw-msft Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

object id is irrelevant here which is different thing from the app id. I am talking about the app id of the application. The service principal provisioned in the tenant for the application should have the app id as part of the servicePrincipalNames array. If you see the appid is not in the servicePrincipalNames of the SP then your change is definitely needed. To get my comment clear, I am talking about the following array in the SP object

   servicePrincipalNames": [
    "http://yugangw-op2",
    "e96b494b-61a2-4a42-a33c-e11ab1f1a6b6" <---this one
  ],

Copy link
Author

@shanepeckham shanepeckham Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yugangw-msft Not sure if I am missing something here, this is the flow:

  1. I have the appId/clientId of my app
  2. I need to get the objectId of the SP associated with my app
  3. The app array does not contain the objectId of the SP
  4. I cannot query the SP by the objectId as I do not have the objectId, I only have the appId
  5. I query the SP by the appId which gives me the SP ObjectId

Copy link
Contributor

@yugangw-msft yugangw-msft Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flow is right. My comment is on step 5 that you don't need to update _resolve_service_principal to take a new argument of appid, rather just pass the app id to the existing argument of identifier which should work.
The reason is the _resolve_service_principal queries for SP using servicePrincipalNames, and appid is one of service principal name, so the query should just work.
If you have tried but not working, please let me know

Copy link
Author

@shanepeckham shanepeckham Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yugangw-msft , thank you, in my tests this was not working for me. Will try again

just pass the app id to the existing argument of identifier which should work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. Is it easy for you to share a repro for me to try out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yugangw-msft , you are right - my tests were bad, will remove appId from query and rebase

# todo: confirm with graph team that a service principal name must be unique
result = list(client.list(filter="servicePrincipalNames/any(c:c eq '{}')".format(identifier)))
if identifier:
result = list(client.list(filter="servicePrincipalNames/any(c:c eq '{}')".format(identifier)))
elif appid:
result = list(client.list(filter="appId eq '{}'".format(appid)))

if result:
return result[0].object_id
if _is_guid(identifier):
Expand Down