...

Source file src/edge-infra.dev/pkg/edge/iam/oauth2/oauth2_auth.go

Documentation: edge-infra.dev/pkg/edge/iam/oauth2

     1  package oauth2
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"edge-infra.dev/pkg/edge/iam/apperror"
     9  	"edge-infra.dev/pkg/edge/iam/client"
    10  	"edge-infra.dev/pkg/edge/iam/config"
    11  	"edge-infra.dev/pkg/edge/iam/log"
    12  	"edge-infra.dev/pkg/edge/iam/profile"
    13  	"edge-infra.dev/pkg/edge/iam/session"
    14  
    15  	"github.com/gin-gonic/gin"
    16  	"github.com/ory/fosite"
    17  )
    18  
    19  // this is just a comment so I can run in CI to test....
    20  func (oauth2 *OAuth2) auth(ctx *gin.Context) error {
    21  	log := log.Get(ctx.Request.Context())
    22  	// extract and validate request parameters
    23  	requester, err := oauth2.fosite.NewAuthorizeRequest(ctx, ctx.Request)
    24  	if err != nil {
    25  		clientID := ctx.Request.URL.Query().Get("client_id")
    26  		rfcErr := fosite.ErrorToRFC6749Error(err)
    27  		// edge-roadmap#9540 Now let's see if we can use the errors predefined in fosite, and grab the description from there
    28  		// & use it to populate the esod page :)
    29  		// we consume the reason on esodError API and use this to look up in fosite, and map the fields to our ESOD :).
    30  		// 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
    31  		// until then returning invalid_auth_request, and I will summarize possible errors in esod description.
    32  		// also possible check if we can implement WriteAuthorizeError of Fosite.
    33  		msg := fmt.Sprintf("failed to process authorize request (client_id=%v). %v", clientID, rfcErr.Error())
    34  		return apperror.NewRedirectError("/esod?reason=invalid_auth_request", msg, rfcErr)
    35  	}
    36  
    37  	cookieSession, _ := oauth2.store.Get(ctx.Request, "oauth2")
    38  	client := requester.GetClient().(*client.Client)
    39  
    40  	// if there's a hint, we let process login hint decide what to do and we are done
    41  	hint := ctx.Query("login_hint")
    42  	if hint != "" {
    43  		cookieSession.Values = make(map[interface{}]interface{})
    44  		cookieSession.Values["client_name"] = client.GetClientName()
    45  		cookieSession.Values["client_id"] = client.GetID()
    46  		// yes, its not up to process hint to decide where we go, next we stop here.
    47  		setRequestURL(cookieSession, ctx)
    48  		setLDFeatureFlags(ctx, cookieSession, requester)
    49  		return oauth2.processLoginHint(ctx, cookieSession, hint, client.GetID(), requester)
    50  	}
    51  
    52  	// without a session or challenge, you loose all your money and back to start
    53  	_, wasGivenChallenge := getChallenge(ctx)
    54  	if cookieSession.IsNew || !wasGivenChallenge {
    55  		cookieSession.Values = make(map[interface{}]interface{})
    56  		cookieSession.Values["client_name"] = client.GetClientName()
    57  		cookieSession.Values["client_id"] = client.GetID()
    58  		setRequestURL(cookieSession, ctx)
    59  		setLDFeatureFlags(ctx, cookieSession, requester)
    60  		if requester.GetRequestedScopes().Has("barcode") {
    61  			ctx.Redirect(http.StatusFound, "/cloud/federate")
    62  			return nil
    63  		}
    64  
    65  		if config.DeviceLoginEnabled() {
    66  			ctx.Redirect(http.StatusFound, "/idp/entry/device")
    67  			return nil
    68  		}
    69  
    70  		ctx.Redirect(http.StatusFound, "/idp/entry/pin")
    71  		return nil
    72  	}
    73  
    74  	// don't challenge me if you don't bring the goods
    75  	err = ValidateChallenge(ctx, client.GetID(), cookieSession)
    76  	if err != nil {
    77  		return apperror.NewRedirectError("/esod?reason=invalid_session", err.Error(), err)
    78  	}
    79  
    80  	// make sure nobody messed about with the request params
    81  	expectedURL := cookieSession.Values["request_url"].(string) + "&challenge=" + cookieSession.Values["continuation"].(string)
    82  	if ctx.Request.URL.String() != expectedURL {
    83  		msg := "possible url tempering detected. requested url not equal to expected url"
    84  		return apperror.NewRedirectError("/esod?reason=invalid_request_url", msg, nil)
    85  	}
    86  
    87  	// are you there?
    88  	subject := cookieSession.Values["sub"].(string)
    89  	if subject == "" {
    90  		msg := "could not find subject on the cookie session, redirecting to esod with invalid_session"
    91  		return apperror.NewRedirectError("/esod?reason=invalid_session", msg, nil)
    92  	}
    93  
    94  	// you are IN ! welcome!
    95  
    96  	// let's just grant all scopes
    97  	for _, scope := range requester.GetRequestedScopes() {
    98  		requester.GrantScope(scope)
    99  	}
   100  
   101  	// create a session for fosite
   102  	authSession := session.NewSession(subject)
   103  
   104  	// give you your permissions
   105  	rls := cookieSession.Values["rls"].(string)
   106  	authSession.SetRls(rls)
   107  
   108  	// add your profile names
   109  	gn, ok := cookieSession.Values["gn"]
   110  	if ok {
   111  		authSession.SetGivenName(gn.(string))
   112  	}
   113  
   114  	fn, ok := cookieSession.Values["fn"]
   115  	if ok {
   116  		authSession.SetFamilyName(fn.(string))
   117  	}
   118  
   119  	n, ok := cookieSession.Values["n"]
   120  	if ok {
   121  		authSession.SetFullName(n.(string))
   122  	}
   123  
   124  	age, ok := cookieSession.Values["age"]
   125  	if ok {
   126  		authSession.SetAge(age.(int))
   127  	}
   128  
   129  	dl, ok := cookieSession.Values["device_login"]
   130  	if ok {
   131  		authSession.SetDeviceLogin(dl.(string))
   132  	}
   133  
   134  	// add your e-mail
   135  	email, ok := cookieSession.Values["email"]
   136  	if ok {
   137  		authSession.SetEmail(email.(string))
   138  	}
   139  
   140  	// add your address
   141  	addressVal, ok := cookieSession.Values["address"]
   142  	if ok {
   143  		address := addressVal.(string)
   144  		var addressClaim profile.AddressClaim
   145  		err = json.Unmarshal([]byte(address), &addressClaim)
   146  		if err != nil {
   147  			log.Error(err, "failed to unmarshal user's address")
   148  		} else {
   149  			addressClaimMap := addressClaim.ToMap()
   150  			authSession.SetAddress(addressClaimMap)
   151  		}
   152  	}
   153  
   154  	// let you in
   155  	responder, err := oauth2.fosite.NewAuthorizeResponse(ctx, requester, authSession)
   156  	if err != nil {
   157  		// this returns two types of rfc6749 errors apart from what authorizeRequester already does.
   158  		// ErrUnsupportedResponseType & ErrUnsupportedResponseMode
   159  		// So we can validate the idea of using the fields in RFC6749 error to populate our ESOD :)
   160  		// 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
   161  		// until then returning invalid_auth_response, and I will summarize possible errors in esod description.
   162  		return apperror.NewRedirectError("/esod?reason=invalid_auth_response", "failed to create an AuthorizeResponder", err)
   163  	}
   164  
   165  	// we're done with the identity authn process, on to token with fosite session
   166  	cookieSession.Options.MaxAge = -1
   167  	err = cookieSession.Save(ctx.Request, ctx.Writer)
   168  	if err != nil {
   169  		log.Error(err, "failed to save cookie session post setting the maxAge")
   170  	}
   171  
   172  	// see ya
   173  	oauth2.fosite.WriteAuthorizeResponse(ctx.Writer, requester, responder)
   174  	return nil
   175  }
   176  

View as plain text