diff --git a/handlers/handlers.go b/handlers/handlers.go index 0a9c3f3b..bf4d928e 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -525,10 +525,14 @@ func getUserInfo(r *http.Request, user *structs.User, customClaims *structs.Cust return err } ptokens.PAccessToken = providerToken.AccessToken + if cfg.GenOAuth.Provider == cfg.Providers.OpenStax { + client := cfg.OAuthClient.Client(context.TODO(), providerToken) + return getUserInfoFromOpenStax(client, user, customClaims, providerToken) + } ptokens.PIdToken = providerToken.Extra("id_token").(string) log.Debugf("ptokens: %+v", ptokens) - // make the "third leg" request back to google to exchange the token for the userinfo + // make the "third leg" request back to provider to exchange the token for the userinfo client := cfg.OAuthClient.Client(context.TODO(), providerToken) if cfg.GenOAuth.Provider == cfg.Providers.Google { return getUserInfoFromGoogle(client, user, customClaims) @@ -565,6 +569,38 @@ func getUserInfoFromOpenID(client *http.Client, user *structs.User, customClaims return nil } + +func getUserInfoFromOpenStax(client *http.Client, user *structs.User, customClaims *structs.CustomClaims, ptoken *oauth2.Token) (rerr error) { + userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) + if err != nil { + return err + } + defer func() { + if err := userinfo.Body.Close(); err != nil { + rerr = err + } + }() + data, _ := ioutil.ReadAll(userinfo.Body) + log.Infof("OpenID userinfo body: ", string(data)) + if err = mapClaims(data, customClaims); err != nil { + log.Error(err) + return err + } + oxUser := structs.OpenStaxUser{} + if err = json.Unmarshal(data, &oxUser); err != nil { + log.Error(err) + return err + } + + oxUser.PrepareUserData() + user.Email = oxUser.Email + user.Name = oxUser.Name + user.Username = oxUser.Username + user.ID = oxUser.ID + user.PrepareUserData() + return nil +} + func getUserInfoFromGoogle(client *http.Client, user *structs.User, customClaims *structs.CustomClaims) (rerr error) { userinfo, err := client.Get(cfg.GenOAuth.UserInfoURL) if err != nil { diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index a71382c0..47c6d03f 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -91,6 +91,7 @@ type OAuthProviders struct { IndieAuth string ADFS string OIDC string + OpenStax string } type branding struct { @@ -126,6 +127,7 @@ var ( IndieAuth: "indieauth", ADFS: "adfs", OIDC: "oidc", + OpenStax: "openstax", } // RequiredOptions must have these fields set for minimum viable config @@ -315,6 +317,15 @@ func Get(key string) string { // BasicTest just a quick sanity check to see if the config is sound func BasicTest() error { + if GenOAuth.Provider != Providers.Google && + GenOAuth.Provider != Providers.GitHub && + GenOAuth.Provider != Providers.IndieAuth && + GenOAuth.Provider != Providers.ADFS && + GenOAuth.Provider != Providers.OIDC && + GenOAuth.Provider != Providers.OpenStax { + return errors.New("configuration error: Unkown oauth provider: "+ GenOAuth.Provider) + } + for _, opt := range RequiredOptions { if !viper.IsSet(opt) { return errors.New("configuration error: required configuration option " + opt + " is not set") @@ -530,6 +541,7 @@ func SetDefaults() { setDefaultsADFS() configureOAuthClient() } else { + // IndieAuth, OIDC, OpenStax configureOAuthClient() } } diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index 81d96e6b..c111c8cd 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -26,7 +26,9 @@ type User struct { // PrepareUserData implement PersonalData interface func (u *User) PrepareUserData() { - u.Username = u.Email + if u.Username == "" { + u.Username = u.Email + } } // GoogleUser is a retrieved and authentiacted user from Google. @@ -93,6 +95,31 @@ func (u *IndieAuthUser) PrepareUserData() { u.Username = u.URL } +type Contact struct { + Type string `json:"type"` + Value string `json:"value"` + Verified bool `json:"is_verified"` +} + +//OpenStaxUser is a retrieved and authenticated user from OpenStax Accounts +type OpenStaxUser struct { + User + Contacts []Contact `json:"contact_infos"` +} + +// PrepareUserData implement PersonalData interface +func (u *OpenStaxUser) PrepareUserData() { + if u.Email == "" { + // assuming first contact of type "EmailAddress" + for _, c := range u.Contacts { + if c.Type == "EmailAddress" && c.Verified { + u.Email = c.Value + break + } + } + } +} + // Team has members and provides acess to sites type Team struct { Name string `json:"name" mapstructure:"name"`