Skip to content

Conversation

bojeil-google
Copy link
Contributor

Implements Auth provider configuration management APIs for configuring SAML and OIDC providers.

listProviderConfigs(
  options: admin.auth.AuthProviderConfigFilter
): Promise<admin.auth.ListProviderConfigResults>;
getProviderConfig(providerId: string): Promise<admin.auth.AuthProviderConfig>
deleteProviderConfig(providerId: string): Promise<void>;
updateProviderConfig(
  providerId: string, updatedConfig: admin.auth.UpdateAuthProviderRequest
): Promise<admin.auth.AuthProviderConfig>;
createProviderConfig(
  config: admin.auth.AuthProviderConfig
): Promise<admin.auth.AuthProviderConfig>;

…g SAML and OIDC providers.

```
listProviderConfigs(
  options: admin.auth.AuthProviderConfigFilter
): Promise<admin.auth.ListProviderConfigResults>;
getProviderConfig(providerId: string): Promise<admin.auth.AuthProviderConfig>
deleteProviderConfig(providerId: string): Promise<void>;
updateProviderConfig(
  providerId: string, updatedConfig: admin.auth.UpdateAuthProviderRequest
): Promise<admin.auth.AuthProviderConfig>;
createProviderConfig(
  config: admin.auth.AuthProviderConfig
): Promise<admin.auth.AuthProviderConfig>;
```
Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

Haven't gone into the unit tests yet. But I've pointed out a bunch of potential changes in the rest of the code. Looks mostly good. Lets try to reduce the use of any wherever possible.

request.pageSize > MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`Required "maxResults" must be a positive non-zero number that does not exceed ` +
Copy link
Contributor

Choose a reason for hiding this comment

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

May be "positive integer that does not...". Positive implies non-zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

constructor(app: FirebaseApp) {
this.httpClient = new AuthorizedHttpClient(app);
this.authUrlBuilder = new AuthResourceUrlBuilder(utils.getProjectId(app), 'v1');
this.projectConfigUrlBuilder = new AuthResourceUrlBuilder(utils.getProjectId(app), 'v2beta1');
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Refactor getProjectId(app) into a constant.

const projectId = utils.getProjectId(app);
this.authUrlBuilder = ...
this.projectConfigUrlBuilder = ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

*
* @param {number=} maxResults The page size, 100 if undefined. This is also the maximum
* allowed limit.
* @param {string=} pageToken The next page token. If not specified, returns OIDC configurations starting
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove "starting"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* @param {string} providerId The provider identifier of the configuration to lookup.
* @return {Promise<object>} A promise that resolves with the provider configuration information.
*/
public getOAuthIdpConfig(providerId: string): Promise<object> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be declared to return a more specific type than object?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

Looks pretty good. I've gone through both impl and tests, and made several comments.

I also think we probably should have done this in 2 PRs (one for OIDC and one for SAML). Lets work on making smaller PRs in the future.

src/auth/auth.ts Outdated
* meeting the filter requirements.
*/
public listProviderConfigs(options: AuthProviderConfigFilter): Promise<ListProviderConfigResults> {
const processResponse = (response: any, providerConfigs: AuthProviderConfig[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Declare return type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

for (const key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
let maskList: string[] = [];
maskList = generateUpdateMask(obj[key]);
Copy link
Contributor

Choose a reason for hiding this comment

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

const maskList = generateUpdateMask(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

export class FirebaseAuthRequestHandler {
private httpClient: AuthorizedHttpClient;
private authUrlBuilder: AuthResourceUrlBuilder;
private projectConfigUrlBuilder: AuthResourceUrlBuilder;
Copy link
Contributor

Choose a reason for hiding this comment

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

All these can be readonly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

pageToken,
};
// Remove next page token if not provided.
if (typeof request.pageToken === 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can work around this by putting an explicit any on request. Or even:

const request: {pageSize: number, pageToken?: string} = ...;

is not too much of a hassle. But I'm ok if you choose to leave this as is for this PR.

// Stub listOAuthIdpConfigs to return expected response.
const listConfigsStub = sinon
.stub(FirebaseAuthRequestHandler.prototype, 'listOAuthIdpConfigs')
.returns(Promise.resolve(listConfigsResponse));
Copy link
Contributor

Choose a reason for hiding this comment

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

.resolves(listConfigsResponse). Here and elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Stub listOAuthIdpConfigs to throw a backend error.
const listConfigsStub = sinon
.stub(FirebaseAuthRequestHandler.prototype, 'listOAuthIdpConfigs')
.returns(Promise.reject(expectedError));
Copy link
Contributor

Choose a reason for hiding this comment

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

.rejects(expectedError). Here and elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

throw new Error('Unexpected success');
})
.catch((error) => {
expect(error).to.have.property('code', 'auth/argument-error');
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we do .should.eventually.be.rejected.and.have.property here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

providerConfigs: [],
};

it('should resolve on listInboundSamlConfigs request success with configs in response', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

should resolve on success with configs in response

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

describe('getOAuthIdpConfig()', () => {
const providerId = 'oidc.provider';
const path = `/v2beta1/projects/project_id/oauthIdpConfigs/${providerId}`;
const method = 'GET';
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Calling this getMethod or httpGet would make the rest of the code more readable. Or even using the string literal as callParams(path, 'GET', {}) is not a bad idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed to expectedHttpMethod here and all the new proposed APIs We need to assert this value in all the suite test. It is better not to hardcode it.

Copy link
Contributor Author

@bojeil-google bojeil-google left a comment

Choose a reason for hiding this comment

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

Appreciate the review.
Regarding the PR being too long, as mentioned in another comment, the way we have been doing things, it is unlikely we can split into multiple PRs since partial features would be available in master before launchcals are approved.
What I can do going forward is split into smaller commits and request reviews one part at a time.

src/auth/auth.ts Outdated
* meeting the filter requirements.
*/
public listProviderConfigs(options: AuthProviderConfigFilter): Promise<ListProviderConfigResults> {
const processResponse = (response: any, providerConfigs: AuthProviderConfig[]) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`Required "maxResults" must be a positive integer that does not exceed ` +
`the allowed ${MAX_LIST_PROVIDER_CONFIGURATION_PAGE_SIZE}.`,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

export class FirebaseAuthRequestHandler {
private httpClient: AuthorizedHttpClient;
private authUrlBuilder: AuthResourceUrlBuilder;
private projectConfigUrlBuilder: AuthResourceUrlBuilder;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

throw new Error('Unexpected success');
})
.catch((error) => {
expect(error).to.have.property('code', 'auth/argument-error');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

providerConfigs: [],
};

it('should resolve on listOAuthIdpConfigs request success with configs in response', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Stub listOAuthIdpConfigs to return expected response.
const listConfigsStub = sinon
.stub(FirebaseAuthRequestHandler.prototype, 'listOAuthIdpConfigs')
.returns(Promise.resolve(listConfigsResponse));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Stub listOAuthIdpConfigs to throw a backend error.
const listConfigsStub = sinon
.stub(FirebaseAuthRequestHandler.prototype, 'listOAuthIdpConfigs')
.returns(Promise.reject(expectedError));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

providerConfigs: [],
};

it('should resolve on listInboundSamlConfigs request success with configs in response', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor

@hiranya911 hiranya911 left a comment

Choose a reason for hiding this comment

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

Thanks for making the changes. LGTM 👍

@hiranya911
Copy link
Contributor

As for managing large changes, this is what I would recommend.

  1. Create a branch for the new feature and enable branch protection for it (you can ask me or another Firebase org admin to do this for you).
git checkout -b auth-id-providers master
// enable protection for auth-id-providers
  1. Then create a series of pull requests against the new branch. Branch protection will make sure that PRs have to be reviewed and CI checks must pass before they can be merged.
git checkout -b impl-part1 auth-idp-providers
// create PR, review and merge

git checkout -b impl-part2 auth-idp-providers
// create PR, review and merge
  1. When the entire feature is implemented, we can merge the new branch to master. This can be done in a PR from the new branch to the master.

@bojeil-google bojeil-google merged commit 9f770b4 into master Apr 16, 2019
@hiranya911 hiranya911 deleted the provider-config branch May 18, 2021 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants