Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Extends 'entra app add/set' commands with new parameter --allowPublicClientFlow. #5908

Closed
Closed
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
9 changes: 9 additions & 0 deletions docs/docs/cmd/entra/app/app-add.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ m365 entra appregistration add [options]

`--save`
: Use to store the information about the created app in a local file.

`--allowPublicClientFlows`
: Enable the allow public client flows feature on the app registration.
```

<Global />
Expand Down Expand Up @@ -192,6 +195,12 @@ Create new Entra app registration with a certificate
m365 entra app add --name 'My Entra app' --certificateDisplayName "Some certificate name" --certificateFile "c:\temp\some-certificate.cer"
```

Create a new Entra app registration with the allow public client flows feature enabled.

```sh
m365 entra app add --name 'My Entra app' --allowPublicClientFlows
```

## Response

### Standard response
Expand Down
9 changes: 9 additions & 0 deletions docs/docs/cmd/entra/app/app-set.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ m365 entra appregistration set [options]

`--certificateDisplayName [certificateDisplayName]`
: Display name for the certificate. If not given, the displayName will be set to the certificate subject. When specified, also specify either `certificateFile` or `certificateBase64Encoded`.

`--allowPublicClientFlows [allowPublicClientFlows]`
: Set to `true` or `false` to toggle the allow public client flows feature on the app registration.
```

<Global />
Expand Down Expand Up @@ -99,6 +102,12 @@ Add a certificate to the app
m365 entra app set --appId e75be2e1-0204-4f95-857d-51a37cf40be8 --certificateDisplayName "Some certificate name" --certificateFile "c:\temp\some-certificate.cer"
```

Enable the allow public client flows feature on the app registration

```sh
m365 entra app set --appId e75be2e1-0204-4f95-857d-51a37cf40be8 --allowPublicClientFlows true
```

## Response

The command won't return a response on success.
91 changes: 91 additions & 0 deletions src/m365/entra/commands/app/app-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7793,4 +7793,95 @@ describe(commands.APP_ADD, () => {
tenantId: ''
}));
});

it('creates Entra app reg with defined name and allowPublicClientFlows option enabled', async () => {
sinon.stub(request, 'get').rejects('Issues GET request');
sinon.stub(request, 'patch').rejects('Issued PATCH request');
sinon.stub(request, 'post').callsFake(async opts => {
if (opts.url === 'https://graph.microsoft.com/v1.0/myorganization/applications' &&
JSON.stringify(opts.data) === JSON.stringify({
"displayName": "My AAD app",
"signInAudience": "AzureADMyOrg",
"isFallbackPublicClient": true
})) {
return {
"id": "5b31c38c-2584-42f0-aa47-657fb3a84230",
"deletedDateTime": null,
"appId": "bc724b77-da87-43a9-b385-6ebaaf969db8",
"applicationTemplateId": null,
"createdDateTime": "2020-12-31T14:44:13.7945807Z",
"displayName": "My AAD app",
"description": null,
"groupMembershipClaims": null,
"identifierUris": [],
"isDeviceOnlyAuthSupported": null,
"isFallbackPublicClient": true,
"notes": null,
"optionalClaims": null,
"publisherDomain": "contoso.onmicrosoft.com",
"signInAudience": "AzureADMyOrg",
"tags": [],
"tokenEncryptionKeyId": null,
"verifiedPublisher": {
"displayName": null,
"verifiedPublisherId": null,
"addedDateTime": null
},
"spa": {
"redirectUris": []
},
"defaultRedirectUri": null,
"addIns": [],
"api": {
"acceptMappedClaims": null,
"knownClientApplications": [],
"requestedAccessTokenVersion": null,
"oauth2PermissionScopes": [],
"preAuthorizedApplications": []
},
"appRoles": [],
"info": {
"logoUrl": null,
"marketingUrl": null,
"privacyStatementUrl": null,
"supportUrl": null,
"termsOfServiceUrl": null
},
"keyCredentials": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [],
"publicClient": {
"redirectUris": []
},
"requiredResourceAccess": [],
"web": {
"homePageUrl": null,
"logoutUrl": null,
"redirectUris": [],
"implicitGrantSettings": {
"enableAccessTokenIssuance": false,
"enableIdTokenIssuance": false
}
}
};
}

throw `Invalid POST request: ${JSON.stringify(opts, null, 2)}`;
});

await command.action(logger, {
options: {
name: 'My AAD app',
allowPublicClientFlows: true
}
});
assert(loggerLogSpy.calledWith({
appId: 'bc724b77-da87-43a9-b385-6ebaaf969db8',
objectId: '5b31c38c-2584-42f0-aa47-657fb3a84230',
tenantId: ''
}));
});
});
11 changes: 10 additions & 1 deletion src/m365/entra/commands/app/app-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ interface Options extends GlobalOptions {
certificateFile?: string;
certificateBase64Encoded?: string;
certificateDisplayName?: string;
allowPublicClientFlows?: boolean;
}

interface AppPermissions {
Expand Down Expand Up @@ -118,7 +119,8 @@ class EntraAppAddCommand extends GraphCommand {
certificateFile: typeof args.options.certificateFile !== 'undefined',
certificateBase64Encoded: typeof args.options.certificateBase64Encoded !== 'undefined',
certificateDisplayName: typeof args.options.certificateDisplayName !== 'undefined',
grantAdminConsent: typeof args.options.grantAdminConsent !== 'undefined'
grantAdminConsent: typeof args.options.grantAdminConsent !== 'undefined',
allowPublicClientFlows: typeof args.options.allowPublicClientFlows !== 'undefined'
});
});
}
Expand Down Expand Up @@ -183,6 +185,9 @@ class EntraAppAddCommand extends GraphCommand {
},
{
option: '--grantAdminConsent'
},
{
option: '--allowPublicClientFlows'
}
);
}
Expand Down Expand Up @@ -330,6 +335,10 @@ class EntraAppAddCommand extends GraphCommand {
applicationInfo.keyCredentials = [newKeyCredential];
}

if (args.options.allowPublicClientFlows) {
applicationInfo.isFallbackPublicClient = true;
}

if (this.verbose) {
await logger.logToStderr(`Creating Microsoft Entra app registration...`);
}
Expand Down
48 changes: 48 additions & 0 deletions src/m365/entra/commands/app/app-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,38 @@ describe(commands.APP_SET, () => {
});
});

it('updates allowPublicClientFlows value for the specified appId', async () => {
sinon.stub(request, 'get').callsFake(async opts => {
if (opts.url === `https://graph.microsoft.com/v1.0/myorganization/applications?$filter=appId eq 'bc724b77-da87-43a9-b385-6ebaaf969db8'&$select=id`) {
return {
value: [{
id: '5b31c38c-2584-42f0-aa47-657fb3a84230'
}]
};
}

throw `Invalid request ${JSON.stringify(opts)}`;
});
sinon.stub(request, 'patch').callsFake(async opts => {
if (opts.url === 'https://graph.microsoft.com/v1.0/myorganization/applications/5b31c38c-2584-42f0-aa47-657fb3a84230' &&
opts.data &&
opts.data.isFallbackPublicClient === true) {
return;
}

throw `Invalid request ${JSON.stringify(opts)}`;
});

await command.action(logger, {
options: {
debug: true,
appId: 'bc724b77-da87-43a9-b385-6ebaaf969db8',
allowPublicClientFlows: true
}
});
});


it('handles error when certificate file cannot be read', async () => {
sinon.stub(request, 'get').callsFake(async opts => {
if (opts.url === `https://graph.microsoft.com/v1.0/myorganization/applications/95cfe30d-ed44-4f9d-b73d-c66560f72e83`) {
Expand Down Expand Up @@ -1339,4 +1371,20 @@ describe(commands.APP_SET, () => {
const actual = await command.validate({ options: { appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', redirectUris: 'https://foo.com', platform: 'web' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when allowPublicClientFlows is specified as true', async () => {
const actual = await command.validate({ options: { appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', allowPublicClientFlows: true } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when allowPublicClientFlows is specified as false', async () => {
const actual = await command.validate({ options: { appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', allowPublicClientFlows: false } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when allowPublicClientFlows is not correct boolean value', async () => {
const actual = await command.validate({ options: { appId: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', allowPublicClientFlows: 'foo' } }, commandInfo);
assert.strictEqual(actual, true);
});

});
42 changes: 40 additions & 2 deletions src/m365/entra/commands/app/app-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface Options extends GlobalOptions {
certificateFile?: string;
certificateBase64Encoded?: string;
certificateDisplayName?: string;
allowPublicClientFlows?: boolean;
}

class EntraAppSetCommand extends GraphCommand {
Expand All @@ -48,6 +49,7 @@ class EntraAppSetCommand extends GraphCommand {
this.#initOptions();
this.#initValidators();
this.#initOptionSets();
this.#initTypes();
}

#initTelemetry(): void {
Expand All @@ -62,7 +64,8 @@ class EntraAppSetCommand extends GraphCommand {
uris: typeof args.options.uris !== 'undefined',
certificateFile: typeof args.options.certificateFile !== 'undefined',
certificateBase64Encoded: typeof args.options.certificateBase64Encoded !== 'undefined',
certificateDisplayName: typeof args.options.certificateDisplayName !== 'undefined'
certificateDisplayName: typeof args.options.certificateDisplayName !== 'undefined',
allowPublicClientFlows: typeof args.options.allowPublicClientFlows !== 'undefined'
});
});
}
Expand All @@ -81,7 +84,11 @@ class EntraAppSetCommand extends GraphCommand {
option: '--platform [platform]',
autocomplete: EntraAppSetCommand.aadApplicationPlatform
},
{ option: '--redirectUrisToRemove [redirectUrisToRemove]' }
{ option: '--redirectUrisToRemove [redirectUrisToRemove]' },
{
option: '--allowPublicClientFlows [allowPublicClientFlows]',
autocomplete: ['true', 'false']
}
);
}

Expand Down Expand Up @@ -118,13 +125,18 @@ class EntraAppSetCommand extends GraphCommand {
this.optionSets.push({ options: ['appId', 'objectId', 'name'] });
}

#initTypes(): void {
this.types.boolean.push('allowPublicClientFlows');
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
await this.showDeprecationWarning(logger, aadCommands.APP_SET, commands.APP_SET);

try {
let objectId = await this.getAppObjectId(args, logger);
objectId = await this.configureUri(args, objectId, logger);
objectId = await this.configureRedirectUris(args, objectId, logger);
objectId = await this.updateAllowPublicClientFlows(args, objectId, logger);
martinlingstuyl marked this conversation as resolved.
Show resolved Hide resolved
await this.configureCertificate(args, objectId, logger);
}
catch (err: any) {
Expand Down Expand Up @@ -171,6 +183,32 @@ class EntraAppSetCommand extends GraphCommand {
return result.id;
}

private async updateAllowPublicClientFlows(args: CommandArgs, objectId: string, logger: Logger): Promise<string> {
if (args.options.allowPublicClientFlows === undefined) {
return objectId;
}

if (this.verbose) {
await logger.logToStderr(`Configuring Entra application AllowPublicClientFlows option...`);
}

const applicationInfo: any = {
isFallbackPublicClient: args.options.allowPublicClientFlows
};

const requestOptions: CliRequestOptions = {
url: `${this.resource}/v1.0/myorganization/applications/${objectId}`,
headers: {
'content-type': 'application/json;odata.metadata=none'
},
responseType: 'json',
data: applicationInfo
};

await request.patch(requestOptions);
return objectId;
}

private async configureUri(args: CommandArgs, objectId: string, logger: Logger): Promise<string> {
if (!args.options.uris) {
return objectId;
Expand Down
Loading