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

feat: add provider to pkg #263

Closed
wants to merge 11 commits into from
Closed

feat: add provider to pkg #263

wants to merge 11 commits into from

Conversation

stebenz
Copy link
Contributor

@stebenz stebenz commented Nov 8, 2023

Definition of Ready

  • I am happy with the code
  • Short description of the feature/issue is added in the pr description
  • PR is linked to the corresponding user story
  • Acceptance criteria are met
  • All open todos and follow ups are defined in a new ticket and justified
  • Deviations from the acceptance criteria and design are agreed with the PO and documented.
  • No debug or dead code
  • My code has no repetitions
  • Critical parts are tested automatically
  • Where possible E2E tests are implemented
  • Documentation/examples are up-to-date
  • All non-functional requirements are met
  • Functionality of the acceptance criteria is checked manually on the dev system.

@stebenz stebenz requested a review from livio-a November 8, 2023 12:52
@stebenz stebenz self-assigned this Nov 8, 2023
Copy link
Member

@livio-a livio-a left a comment

Choose a reason for hiding this comment

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

i remarked the most important points. as noted before, i'd create a next branch to that we can easily make breaking changes and rework the whole SDK. please also add the (basic) examples right in this repo

pkg/info/info.go Outdated
type providerInfoKey struct{}

type ProviderInfo struct {
IntrospectionResponse *oidc.IntrospectionResponse
Copy link
Member

Choose a reason for hiding this comment

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

i wouldn't tie the info to the *oidc.introspectionResponse first of all that can be generic and what about userinfo, non oidc related info, ...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's why the response is part of the struct, if we have other or additional information wie want to add to the context we either can split it or for the moment add it here with a set-function

Comment on lines 26 to 46
type configurationSAML struct{}

func OIDCProviderConfiguration(issuer, keyPath, clientID, clientSecret, port, callbackURL string, scopes []string) *configuration {
return &configuration{
oidc: &configurationOIDC{
issuer: issuer,
keyPath: keyPath,
clientID: clientID,
clientSecret: clientSecret,
port: port,
callbackURL: callbackURL,
scopes: scopes,
},
}
}

func SAMLProviderConfiguration() *configuration {
return &configuration{
saml: &configurationSAML{},
}
}
Copy link
Member

Choose a reason for hiding this comment

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

the constructors are never used and saml is not implemented (IMO just omit it currently)

Comment on lines 32 to 33
redirectURI := fmt.Sprintf("http://localhost:%v%v", oidc.port, oidc.callbackURL)
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure())
Copy link
Member

Choose a reason for hiding this comment

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

why is that statically using localhost and http?

Comment on lines 47 to 56
func marshalUserinfo() rp.CodeExchangeUserinfoCallback[*oidc.IDTokenClaims, *oidc.UserInfo] {
return func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) {
data, err := json.Marshal(info)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

what's the purpose of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can be removed was part of the example and then the function to call can be provided as parameter

Comment on lines 69 to 70
pi := info.ProviderInfoFromContext(ctx)
resp, err := checkIntrospectWithCached(ctx, resourceServer, token, pi.IntrospectionResponse)
Copy link
Member

Choose a reason for hiding this comment

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

just for my understanding: how is this intended? and why is the introspection response always cached?

Copy link
Member

Choose a reason for hiding this comment

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

BTW as it's calling the introspection endpoint, that would mean it's checking for authorization and not authentication

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 only cached if an introspection response is already in the context, as for example the different interceptors are stacked. It's only calling the introspection endpoint to check if the token is still active, so you suggest to remove the introspection call and only check the token itself?

Comment on lines 83 to 104
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

token, err := checkOIDCTokenFromRequest(req)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

pi := info.ProviderInfoFromContext(ctx)
resp, err := checkIntrospectWithCached(ctx, resourceServer, token, pi.IntrospectionResponse)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
ctx = pi.SetIntrospectionResponse(resp).IntoContext(ctx)

if err := checkAuthorization(pi.IntrospectionResponse, requestedClaim, requestedValue); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
next.ServeHTTP(w, req.WithContext(ctx))
Copy link
Member

Choose a reason for hiding this comment

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

mostly redundant (apart from the checkAuthorization) and why can on string claims be checked, resp. shouldn't there be multiple options what to be checked (simple authZ check, role check, custom checks, ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can be expanded, started of with this as an example

Comment on lines 171 to 180
func checkIntrospect(ctx context.Context, resourceServer rs.ResourceServer, token string) (*oidc.IntrospectionResponse, error) {
resp, err := rs.Introspect[*oidc.IntrospectionResponse](ctx, resourceServer, token)
if err != nil {
return nil, ErrInvalidToken
}
if !resp.Active {
return nil, ErrInvalidToken
}
return resp, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

as mentioned before, i'd make that generic so that developers can handle their custom claims (if needed)

)

var (
key = []byte("test1234test1234")
Copy link
Member

Choose a reason for hiding this comment

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

🤨

Copy link
Contributor Author

Choose a reason for hiding this comment

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

leftover from the copy, removed

Comment on lines 8 to 24
type configurationOIDC struct {
issuer string
keyPath string
clientID string
clientSecret string
port string
callbackURL string
scopes []string
}

func (c *configurationOIDC) validRS() bool {
return c.issuer != "" && c.keyPath != ""
}

func (c *configurationOIDC) validRP() bool {
return c.issuer != "" && c.clientID != "" && c.port != "" && c.callbackURL != ""
}
Copy link
Member

Choose a reason for hiding this comment

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

why is there only a single config (authentication and authorization)? e.g. what if i want jwt-profile for both, authentication and authorization?

Copy link
Member

Choose a reason for hiding this comment

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

and IMO it's to OIDC focused. I'd go with a domain or alike on the provider configuration itself, which represents the zitadel domain to use. IMO easy understandable for devs and regardless of what protocol is used later on it can be used. In case of OIDC this will correspond to the issuer, but the same value can then also be used for the client to call the API and so on

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want a combination then you only have to instantiate multiple providers, as maybe for these 2 functions, only the minimum required values are checked.
Changed the issuer to host, port, and insecure attributes, to concatenate redirectURI and issuer from.

@livio-a
Copy link
Member

livio-a commented Feb 28, 2024

closing in favor of the next branch

@livio-a livio-a closed this Feb 28, 2024
@livio-a livio-a deleted the provider branch February 28, 2024 09:18
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