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

[#175227073] Allow updating ADB2C User and introduce Token Name management on Create/Update operations #92

Merged
merged 9 commits into from
Oct 15, 2020

Conversation

AleDore
Copy link
Contributor

@AleDore AleDore commented Oct 12, 2020

This PR is intended to manage ADB2C custom attribute Token Name, in order to allow existing CreateUser and the brand new UpdateUser to manage this custom attribute.

@pagopa-github-bot
Copy link
Contributor

pagopa-github-bot commented Oct 12, 2020

Affected stories

  • 🌟 #175227073: Come ADM, vorrei poter settare l' attributo Token Name all' interno degli attributi custom di B2C programmaticamente.

Generated by 🚫 dangerJS

@AleDore AleDore changed the title [#175227073] Allow updating ADB2C User and introduce Token Name management [#175227073] Allow updating ADB2C User and introduce Token Name management on Create/Update operations Oct 12, 2020
@francescopersico
Copy link
Contributor

When you update a user, setting a different token_name, what happens to already created service? The token_name in the services remains the old one. Can it be a problem?

@gunzip
Copy link
Contributor

gunzip commented Oct 13, 2020

The ratio here is that attributes on user account are default values for newly create services, so I think it's not a problem if the old services maintain the old token name (it must be documented)

@AleDore
Copy link
Contributor Author

AleDore commented Oct 13, 2020

When you update a user, setting a different token_name, what happens to already created service? The token_name in the services remains the old one. Can it be a problem?

Since this operation will be performed only by administrators, I think it will be used only for newly accounts or on existing ones, that will be enabled after the test's phase. It shouldn't be a problem if old services maintain the old token name.

internalErrorHandler("Could not get the ADB2C client", error)
)
.chain(graphRbacManagementClient =>
getUserFromList(graphRbacManagementClient, userPayload.email)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we decode userPayload? It's an input, it can be anything

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, userPayload is validated here

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry didn't see that

Copy link
Contributor Author

@AleDore AleDore left a comment

Choose a reason for hiding this comment

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

Can we merge this? @francescopersico @gunzip ?

@AleDore
Copy link
Contributor Author

AleDore commented Oct 13, 2020

I' ve checked that on updateUser operation, we expect first_name and last_name in the userPayload, but I think that the only information we have about the user to update is email isn't it? cc: @gunzip @francescopersico

@gunzip
Copy link
Contributor

gunzip commented Oct 13, 2020

I think updateUser should work in the same way as the other update methods (ie. UpdateService), retrieving the old user properties and merging the overridden values, what about it?

@AleDore
Copy link
Contributor Author

AleDore commented Oct 13, 2020

I think updateUser should work in the same way as the other update methods (ie. UpdateService), retrieving the old user properties and merging the overridden values, what about it?

Nope because graph API expect a payload containing only the attributes to be updated (so other attributes are not overwritten if not present ), but the question is: Do we always have informations about first_name and last_name while performing UpdateUser operation ? I think that the only information we got in that case is the email associated with the B2C account, so first_name and last_name should be optional in the userPayload used for Update. What do you think?

@gunzip
Copy link
Contributor

gunzip commented Oct 13, 2020

that's right, maybe you can use t.partial(User)for the decoder

@AleDore
Copy link
Contributor Author

AleDore commented Oct 14, 2020

that's right, maybe you can use t.partial(User)for the decoder

Maybe we can define a payload for UpdateUser that has all possible attributes optional.

Comment on lines 76 to 80
first_name: userPayload.first_name
? userPayload.first_name
: user.givenName,
id: updateUserResponse.objectId,
last_name: userPayload.last_name ? userPayload.last_name : user.surname,
Copy link
Contributor

Choose a reason for hiding this comment

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

why this mapping here? can't we just return the exact values ?
first_name -> first_name, last_name -> last_name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

UserCreated has first_name and last_name as required fields. Since userPayload has the same optional attributes, we can map over user attributes as a fallback option.

Copy link
Contributor

Choose a reason for hiding this comment

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

we can make it shorter like

first_name: userPayload.first_name || user.givenName

less cognitive load imho

Copy link
Contributor

@gunzip gunzip Oct 14, 2020

Choose a reason for hiding this comment

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

why do we need the fallback? moreover, it prevent the caller to check if the values are undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why do we need the fallback? moreover, it prevent the caller to check if the values are undefined

Because if the caller doesn't provide first_name or last_name (namely it doesn't want to update first_name or last_name), we must provide the same attributes on UserCreated response because they are required. I think that B2C user always has this attributes ('cause createUser expects them in the request payload as required fields)

Copy link
Contributor

Choose a reason for hiding this comment

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

fallback is not a good idea (it's an arbitrary mapping just to fill value to satisfy the decoder). if we must return these values then they must be retrieved from the original user record otherwise we should relax the constraints on these fields

Copy link
Contributor Author

@AleDore AleDore Oct 14, 2020

Choose a reason for hiding this comment

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

Ok let's create a UserUpdated instead of UserCreated definition that allows direct mapping from userPayload

Comment on lines 76 to 80
first_name: userPayload.first_name
? userPayload.first_name
: user.givenName,
id: updateUserResponse.objectId,
last_name: userPayload.last_name ? userPayload.last_name : user.surname,
Copy link
Contributor

Choose a reason for hiding this comment

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

we can make it shorter like

first_name: userPayload.first_name || user.givenName

less cognitive load imho

id: updateUserResponse.objectId,
last_name: userPayload.last_name ? userPayload.last_name : user.surname,
token_name: userPayload.token_name
} as UserCreated)
Copy link
Contributor

@balanza balanza Oct 14, 2020

Choose a reason for hiding this comment

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

Instead of this manual cast, should we use a codec?

Comment on lines 73 to 80
).chain(updateUserResponse =>
UserCreated.decode({
email,
first_name: userPayload.first_name || user.givenName,
id: updateUserResponse.objectId,
last_name: userPayload.last_name || user.surname,
token_name: userPayload.token_name
}).fold(errs => fromLeft(toError(errs)), usr => taskEither.of(usr))
Copy link
Contributor

@balanza balanza Oct 14, 2020

Choose a reason for hiding this comment

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

what about

Suggested change
).chain(updateUserResponse =>
UserCreated.decode({
email,
first_name: userPayload.first_name || user.givenName,
id: updateUserResponse.objectId,
last_name: userPayload.last_name || user.surname,
token_name: userPayload.token_name
}).fold(errs => fromLeft(toError(errs)), usr => taskEither.of(usr))
).chain(updateUserResponse =>
fromEither(UserCreated.decode({
email,
first_name: userPayload.first_name || user.givenName,
id: updateUserResponse.objectId,
last_name: userPayload.last_name || user.surname,
token_name: userPayload.token_name
})).mapLeft(toError)

Comment on lines 7 to 8
import { fromLeft } from "fp-ts/lib/TaskEither";
import { taskEither, TaskEither, tryCatch } from "fp-ts/lib/TaskEither";
Copy link
Contributor

Choose a reason for hiding this comment

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

If you accept that suggestion you'll need this, too

Suggested change
import { fromLeft } from "fp-ts/lib/TaskEither";
import { taskEither, TaskEither, tryCatch } from "fp-ts/lib/TaskEither";
import { fromEither, taskEither, TaskEither, tryCatch } from "fp-ts/lib/TaskEither";

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad, you' re right 😄

@codecov-io
Copy link

codecov-io commented Oct 14, 2020

Codecov Report

Merging #92 into master will decrease coverage by 0.97%.
The diff coverage is 69.64%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #92      +/-   ##
==========================================
- Coverage   83.94%   82.96%   -0.98%     
==========================================
  Files          44       48       +4     
  Lines        1489     1603     +114     
  Branches      124      128       +4     
==========================================
+ Hits         1250     1330      +80     
- Misses        234      268      +34     
  Partials        5        5              
Impacted Files Coverage Δ
utils/healthcheck.ts 41.02% <41.02%> (ø)
CreateUser/handler.ts 87.17% <75.00%> (+0.33%) ⬆️
utils/config.ts 75.00% <75.00%> (ø)
Info/handler.ts 83.33% <85.71%> (ø)
UpdateUser/handler.ts 89.13% <89.13%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 33442c2...c56eee6. Read the comment docs.

@AleDore AleDore requested a review from gunzip October 14, 2020 15:41
Comment on lines 7 to 8
import { fromEither } from "fp-ts/lib/TaskEither";
import { TaskEither, tryCatch } from "fp-ts/lib/TaskEither";
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to import twice

Suggested change
import { fromEither } from "fp-ts/lib/TaskEither";
import { TaskEither, tryCatch } from "fp-ts/lib/TaskEither";
import { fromEither, TaskEither, tryCatch } from "fp-ts/lib/TaskEither";

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It' s so strange that lint doesn't report a non grouped import 😄

@AleDore AleDore requested a review from balanza October 14, 2020 16:04
@AleDore
Copy link
Contributor Author

AleDore commented Oct 15, 2020

Can we merge this? @balanza @BurnedMarshal

mockGetToken.mockImplementation(() => {
return Promise.resolve(undefined);
});
const mockUsersCreate = jest.fn();
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be this mockUsersUpdate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2948303

fakeRequestPayload
);

expect(response.kind).toEqual("IResponseErrorInternal");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we relate the behaviour of this test with the test conditions?
For example

Suggested change
expect(response.kind).toEqual("IResponseErrorInternal");
expect(mockUsersCreate).toBeCalledTimes(1);
expect(response).toEqual({
apply: expect.any(Function),
detail: expectedError,
kind: "IResponseErrorInternal"
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2948303

@AleDore AleDore requested a review from BurnedMarshal October 15, 2020 15:25
Copy link
Contributor

@BurnedMarshal BurnedMarshal left a comment

Choose a reason for hiding this comment

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

lgtm

@AleDore AleDore merged commit 6b7b7dc into master Oct 15, 2020
@AleDore AleDore deleted the 175227073_update_b2c_user_and_token_name_mgmt branch October 15, 2020 15:31
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.

7 participants