package oauth2 import ( "encoding/json" "fmt" "net/http" "edge-infra.dev/pkg/edge/iam/apperror" "edge-infra.dev/pkg/edge/iam/client" "edge-infra.dev/pkg/edge/iam/config" "edge-infra.dev/pkg/edge/iam/log" "edge-infra.dev/pkg/edge/iam/profile" "edge-infra.dev/pkg/edge/iam/session" "github.com/gin-gonic/gin" "github.com/ory/fosite" ) // this is just a comment so I can run in CI to test.... func (oauth2 *OAuth2) auth(ctx *gin.Context) error { log := log.Get(ctx.Request.Context()) // extract and validate request parameters requester, err := oauth2.fosite.NewAuthorizeRequest(ctx, ctx.Request) if err != nil { clientID := ctx.Request.URL.Query().Get("client_id") rfcErr := fosite.ErrorToRFC6749Error(err) // edge-roadmap#9540 Now let's see if we can use the errors predefined in fosite, and grab the description from there // & use it to populate the esod page :) // we consume the reason on esodError API and use this to look up in fosite, and map the fields to our ESOD :). // the errors in fosite are plain struct variables, we do not have a map which can iter over, might need to write our own handling here // until then returning invalid_auth_request, and I will summarize possible errors in esod description. // also possible check if we can implement WriteAuthorizeError of Fosite. msg := fmt.Sprintf("failed to process authorize request (client_id=%v). %v", clientID, rfcErr.Error()) return apperror.NewRedirectError("/esod?reason=invalid_auth_request", msg, rfcErr) } cookieSession, _ := oauth2.store.Get(ctx.Request, "oauth2") client := requester.GetClient().(*client.Client) // if there's a hint, we let process login hint decide what to do and we are done hint := ctx.Query("login_hint") if hint != "" { cookieSession.Values = make(map[interface{}]interface{}) cookieSession.Values["client_name"] = client.GetClientName() cookieSession.Values["client_id"] = client.GetID() // yes, its not up to process hint to decide where we go, next we stop here. setRequestURL(cookieSession, ctx) setLDFeatureFlags(ctx, cookieSession, requester) return oauth2.processLoginHint(ctx, cookieSession, hint, client.GetID(), requester) } // without a session or challenge, you loose all your money and back to start _, wasGivenChallenge := getChallenge(ctx) if cookieSession.IsNew || !wasGivenChallenge { cookieSession.Values = make(map[interface{}]interface{}) cookieSession.Values["client_name"] = client.GetClientName() cookieSession.Values["client_id"] = client.GetID() setRequestURL(cookieSession, ctx) setLDFeatureFlags(ctx, cookieSession, requester) if requester.GetRequestedScopes().Has("barcode") { ctx.Redirect(http.StatusFound, "/cloud/federate") return nil } if config.DeviceLoginEnabled() { ctx.Redirect(http.StatusFound, "/idp/entry/device") return nil } ctx.Redirect(http.StatusFound, "/idp/entry/pin") return nil } // don't challenge me if you don't bring the goods err = ValidateChallenge(ctx, client.GetID(), cookieSession) if err != nil { return apperror.NewRedirectError("/esod?reason=invalid_session", err.Error(), err) } // make sure nobody messed about with the request params expectedURL := cookieSession.Values["request_url"].(string) + "&challenge=" + cookieSession.Values["continuation"].(string) if ctx.Request.URL.String() != expectedURL { msg := "possible url tempering detected. requested url not equal to expected url" return apperror.NewRedirectError("/esod?reason=invalid_request_url", msg, nil) } // are you there? subject := cookieSession.Values["sub"].(string) if subject == "" { msg := "could not find subject on the cookie session, redirecting to esod with invalid_session" return apperror.NewRedirectError("/esod?reason=invalid_session", msg, nil) } // you are IN ! welcome! // let's just grant all scopes for _, scope := range requester.GetRequestedScopes() { requester.GrantScope(scope) } // create a session for fosite authSession := session.NewSession(subject) // give you your permissions rls := cookieSession.Values["rls"].(string) authSession.SetRls(rls) // add your profile names gn, ok := cookieSession.Values["gn"] if ok { authSession.SetGivenName(gn.(string)) } fn, ok := cookieSession.Values["fn"] if ok { authSession.SetFamilyName(fn.(string)) } n, ok := cookieSession.Values["n"] if ok { authSession.SetFullName(n.(string)) } age, ok := cookieSession.Values["age"] if ok { authSession.SetAge(age.(int)) } dl, ok := cookieSession.Values["device_login"] if ok { authSession.SetDeviceLogin(dl.(string)) } // add your e-mail email, ok := cookieSession.Values["email"] if ok { authSession.SetEmail(email.(string)) } // add your address addressVal, ok := cookieSession.Values["address"] if ok { address := addressVal.(string) var addressClaim profile.AddressClaim err = json.Unmarshal([]byte(address), &addressClaim) if err != nil { log.Error(err, "failed to unmarshal user's address") } else { addressClaimMap := addressClaim.ToMap() authSession.SetAddress(addressClaimMap) } } // let you in responder, err := oauth2.fosite.NewAuthorizeResponse(ctx, requester, authSession) if err != nil { // this returns two types of rfc6749 errors apart from what authorizeRequester already does. // ErrUnsupportedResponseType & ErrUnsupportedResponseMode // So we can validate the idea of using the fields in RFC6749 error to populate our ESOD :) // the errors in fosite are plain struct variables, we do not have a map which can iter over, might need to write our own handling here // until then returning invalid_auth_response, and I will summarize possible errors in esod description. return apperror.NewRedirectError("/esod?reason=invalid_auth_response", "failed to create an AuthorizeResponder", err) } // we're done with the identity authn process, on to token with fosite session cookieSession.Options.MaxAge = -1 err = cookieSession.Save(ctx.Request, ctx.Writer) if err != nil { log.Error(err, "failed to save cookie session post setting the maxAge") } // see ya oauth2.fosite.WriteAuthorizeResponse(ctx.Writer, requester, responder) return nil }