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

SAML Implementation in Kratos #3

Closed
wants to merge 411 commits into from
Closed

SAML Implementation in Kratos #3

wants to merge 411 commits into from

Conversation

ThibHrrd
Copy link

@ThibHrrd ThibHrrd commented May 3, 2022

SAML Implementation

Related issue(s)

ory#1928

@ThibHrrd ThibHrrd requested review from alexGNX, Stoakes and sebferrer May 3, 2022 12:38
Copy link
Member

@Stoakes Stoakes left a comment

Choose a reason for hiding this comment

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

Nice work !

Some questions and a few comments. Mostly on English, Golang & code style.

@@ -1932,6 +1932,81 @@
]
}
},
"/self-service/saml/metadata":{
"get":{
"description": "This endpoint is for the IDP to obtain kratos metadata",
Copy link
Member

Choose a reason for hiding this comment

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

The style for the other descriptions is slightly different.

"This endpoints returns kratos metadata as part of a SAMl login" would be closer to what is done on other endpoints.

},
"/self-service/saml/idp":{
"get":{
"description": "This endpoint is to redirect the user to the idp auth flow",
Copy link
Member

Choose a reason for hiding this comment

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

Same as above: style for description text is different from other already existing endpoints.

"get":{
"description": "This endpoint is for the IDP to obtain kratos metadata",
"operationId": "getSamlMetadata",
"response": {
Copy link
Member

Choose a reason for hiding this comment

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

I'm missing a few bits on SAML flow, so this might be an irrelevant comment; is it expected there's no 200 response ?

@@ -2006,6 +2006,81 @@
]
}
},
"/self-service/saml/idp":{
"get":{
"description": "This endpoint is to redirect the user to the idp auth flow",
Copy link
Member

Choose a reason for hiding this comment

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

All other descriptions are something like "This endpoint + verb at the active form".

},
"mapper_url": {
"title": "Jsonnet Mapper URL",
"description": "The URL where the jsonnet source is located for mapping the provider's data to Ory Kratos data.",
Copy link
Member

Choose a reason for hiding this comment

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

misc english wording

Suggested change
"description": "The URL where the jsonnet source is located for mapping the provider's data to Ory Kratos data.",
"description": "Location for the jsonnet mapping between provider's data and Ory Kratos data",


// does not need sorting because there is only one field
c.SetCSRF(s.d.GenerateCSRFToken(r))
// AddSamlProviders(c, conf.Providers, message)
Copy link
Member

Choose a reason for hiding this comment

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

Commented code can be removed

return err
}

func (s *Strategy) CountActiveCredentials(cc map[identity.CredentialsType]identity.Credentials) (count int, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

Personal preference: returning explicitly values instead of using named return argument. I do so, because I find it less error prone.

return
}

func (s *Strategy) CountActiveFirstFactorCredentials(cc map[identity.CredentialsType]identity.Credentials) (count int, err error) {
Copy link
Member

Choose a reason for hiding this comment

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

This function requires a bit of reading to fully understand what it does; it might be interesting to add a comment describing what it does.

// This file is called when the /ACS receives an assertion, the method below allows you to define whether to register or login the user indicated in the assertion
//#################

// Handle SAML Assertion and process to either login or register
Copy link
Member

Choose a reason for hiding this comment

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

golang standard comment format starts with variable of function name.

Kratos uses a different one:

To be adapted based on your knowledge of ory coding style.

if err = s.processRegistration(w, r, registerFlow, provider, claims); err != nil {
if i == nil {
return nil, s.handleError(w, r, registerFlow, provider.Config().ID, nil, err)
} else {
Copy link
Member

Choose a reason for hiding this comment

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

Unused else: No need to add a else when last statement of an if is a return.

Here and below

Copy link

@psauvage0 psauvage0 left a comment

Choose a reason for hiding this comment

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

Great work ! :D

The main problem I see is with the attribute mapping that only works with a fixed list of attributes names.

Otherwise it's about small code quality improvements. There are some pointed out by the linter that I didn't bother to comment as they are already shown in the PR builds.

I honestly think that we could already take reviews from the Ory team & community, as there are maybe some things about how they want the code to be organized.

"Microsoft Active Directory"
]
},
"public_cert_path": {

Choose a reason for hiding this comment

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

certificate_path ? Or public_key_certificate_path ?

}
]
},
"attributes_map": {

Choose a reason for hiding this comment

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

The attributes mapping is given in the config ? What if I have an attribute that is not provided here ? For example group ?

Copy link
Author

Choose a reason for hiding this comment

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

For the moment, this part is not configurable, that is to say that the identity cannot have more fields than what is present here.

@@ -1185,6 +1185,51 @@ paths:
summary: Create a Logout URL for Browsers
tags:
- v0alpha2
/self-service/methods/saml/auth:

Choose a reason for hiding this comment

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

Not related to the doc but to the route of this call (I don't know where the route is defined).

In the Ory Documentation for the API, we can see that when there are different calls to make for browser vs API, they usually end the route with /browser or /api. Maybe the same can be applied here ?

Copy link
Member

Choose a reason for hiding this comment

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

We did it on purpose based on a misunderstanding, indeed we should change it back

}

// Key pair to encrypt and sign SAML requests
keyPair, err := tls.LoadX509KeyPair(strings.Replace(c.SAMLProviders[len(c.SAMLProviders)-1].PublicCertPath, "file://", "", 1), strings.Replace(c.SAMLProviders[len(c.SAMLProviders)-1].PrivateKeyPath, "file://", "", 1))

Choose a reason for hiding this comment

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

No Out Of Bound possible here ?

var idpMetadata *samlidp.EntityDescriptor

// We check if the metadata file is provided
if c.SAMLProviders[len(c.SAMLProviders)-1].IDPInformation["idp_metadata_url"] != "" {

Choose a reason for hiding this comment

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

OOB ?
Also, you can check if a map entry is set with something like if value, ok := mymap["key"]; ok { ...

Suggested change
if c.SAMLProviders[len(c.SAMLProviders)-1].IDPInformation["idp_metadata_url"] != "" {
if metadataURL, ok := c.SAMLProviders[len(c.SAMLProviders)-1].IDPInformation["idp_metadata_url"]; ok {

return
}

func (s *Strategy) CountActiveMultiFactorCredentials(cc map[identity.CredentialsType]identity.Credentials) (count int, err error) {

Choose a reason for hiding this comment

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

Not implemented ?

Copy link
Member

Choose a reason for hiding this comment

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

Not yet. This implementation isn't required since a long time, so we had to do it like this on our last rebase.

return nil, err
}
} else {
// The user already exist in database so we log him

Choose a reason for hiding this comment

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

This makes me think : what happens when the IDP deletes a user ? I guess the user remains in the SP forever... What are the consequences of that ? It should not be a problem at all, but we could investigate this case a bit

Copy link
Author

Choose a reason for hiding this comment

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

This is indeed what will happen but it is complicated for the SP to know that the IDP has deleted a user

return nil, nil
}

// Method not used but necessary to implement the interface

Choose a reason for hiding this comment

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

I find this weird but if you say so 🤨

Copy link
Member

Choose a reason for hiding this comment

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

Here's the kind of error we get otherwise:

# github.com/ory/kratos/selfservice/strategy/saml/strategy
selfservice/strategy/saml/strategy/strategy_login.go:25:24: cannot use new(Strategy) (value of type *Strategy) as type login.Strategy in variable declaration:
        *Strategy does not implement login.Strategy (missing Login method)

Copy link

@psauvage0 psauvage0 May 4, 2022

Choose a reason for hiding this comment

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

What I found weird was that the method is called "Login", so I would expect that it is a pretty important method to implement in an IAM.

Edit: As we discussed, it's because of the rebase against the origin. It's a new method added to the interface, probably worth checking where this method is called, to ensure that it is safe to not implement it

is "gotest.tools/assert/cmp"
)

func TestGetAndDecryptAssertion(t *testing.T) {

Choose a reason for hiding this comment

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

Unrelated to the code : it would be interesting to discuss test strategy with the Ory community. It seems there is not a lot of info on what is considered a good level of testing on CONTRIBUING.md file (should we cover all branches of a functions ? Should we test for edge cases ? Or simply testing the main intent of the function is OK ?)

@@ -3636,6 +3653,48 @@
]
}
},
"/self-service/methods/saml/auth": {

Choose a reason for hiding this comment

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

Same thing as before about the route : /browser vs /api routes

martinei and others added 15 commits November 28, 2022 18:50
Previously the context was not propagated to the http client. As a result the (instrumented) client did not find the existing span and the sapns for outgoing http request have been orphains.

With this simple Fix they are now children of the corresponding webhook spans.
…2917)

Request cookies were already available in raw form in
the ctx.request_headers top-level argument to the Jsonnet snippet.
Parsing cookies in Jsonnet is tedious and error-prone, though, so
we parse them internally for convenience.
BREAKING CHANGE: The `/admin/courier/messages` endpoint now uses `keysetpagination` instead.
Signed-off-by: ThibaultHerard <[email protected]>

Co-authored-by: sebferrer <[email protected]>
Co-authored-by: psauvage <[email protected]>
Co-authored-by: alexGNX <[email protected]>
Co-authored-by: Stoakes <[email protected]>
Signed-off-by: ThibaultHerard <[email protected]>

Co-authored-by: sebferrer <[email protected]>
Signed-off-by: ThibaultHerard <[email protected]>

Co-authored-by: sebferrer <[email protected]>
Signed-off-by: sebferrer <[email protected]>

Co-authored-by: ThibaultHerard <[email protected]>
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.