From 2dfcccd241c915fc1991fd2d4be4665414ff5f9c Mon Sep 17 00:00:00 2001 From: Simon Gottschlag Date: Wed, 6 Feb 2019 15:49:14 +0100 Subject: [PATCH] Add initial support for ADFS (#5) --- handlers/handlers.go | 73 ++++++++++++++++++++++++++++++++++++++++++ pkg/cfg/cfg.go | 10 +++--- pkg/structs/structs.go | 16 +++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index 475a035f..7ccc3a3e 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -10,6 +10,8 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "net/url" + "strconv" "strings" log "github.com/Sirupsen/logrus" @@ -424,6 +426,8 @@ func getUserInfo(r *http.Request, user *structs.User) error { // indieauth sends the "me" setting in json back to the callback, so just pluck it from the callback if cfg.GenOAuth.Provider == cfg.Providers.IndieAuth { return getUserInfoFromIndieAuth(r, user) + } else if cfg.GenOAuth.Provider == cfg.Providers.ADFS { + return getUserInfoFromADFS(r, user) } providerToken, err := cfg.OAuthClient.Exchange(oauth2.NoContext, r.URL.Query().Get("code")) @@ -569,6 +573,75 @@ func getUserInfoFromIndieAuth(r *http.Request, user *structs.User) error { return nil } +// More info: https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/overview/ad-fs-scenarios-for-developers#supported-scenarios +func getUserInfoFromADFS(r *http.Request, user *structs.User) error { + code := r.URL.Query().Get("code") + log.Errorf("code: %s", code) + + formData := url.Values{} + formData.Set("code", code) + formData.Set("grant_type", "authorization_code") + formData.Set("resource", cfg.GenOAuth.RedirectURL) + formData.Set("client_id", cfg.GenOAuth.ClientID) + formData.Set("redirect_uri", cfg.GenOAuth.RedirectURL) + formData.Set("client_secret", cfg.GenOAuth.ClientSecret) + + req, err := http.NewRequest("POST", cfg.GenOAuth.TokenURL, strings.NewReader(formData.Encode())) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(formData.Encode()))) + req.Header.Set("Accept", "application/json") + + // v := url.Values{} + // userinfo, err := client.PostForm(cfg.GenOAuth.UserInfoURL, v) + + client := &http.Client{} + userinfo, err := client.Do(req) + + if err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + return err + } + defer userinfo.Body.Close() + + body, _ := ioutil.ReadAll(userinfo.Body) + + var tokenRes struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + IDToken string `json:"id_token"` + ExpiresIn int64 `json:"expires_in"` // relative seconds from now + } + + if err := json.Unmarshal(body, &tokenRes); err != nil { + log.Errorf("oauth2: cannot fetch token: %v", err) + return nil + } + + s := strings.Split(tokenRes.IDToken, ".") + if len(s) < 2 { + log.Error("jws: invalid token received") + return nil + } + + idToken, err := base64.RawURLEncoding.DecodeString(s[1]) + if err != nil { + log.Error(err) + return nil + } + + adfsUser := structs.ADFSUser{} + json.Unmarshal([]byte(idToken), &adfsUser) + log.Println("adfs adfsUser: ", adfsUser) + + adfsUser.PrepareUserData() + user.Username = adfsUser.UPN + log.Debug(user) + return nil +} + // the standard error // this is captured by nginx, which converts the 401 into 302 to the login page func error401(w http.ResponseWriter, r *http.Request, ae AuthError) { diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index b8177053..cbb76b80 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -79,6 +79,7 @@ type OAuthProviders struct { Google string GitHub string IndieAuth string + ADFS string OIDC string } @@ -113,6 +114,7 @@ var ( Google: "google", GitHub: "github", IndieAuth: "indieauth", + ADFS: "adfs", OIDC: "oidc", } @@ -213,12 +215,12 @@ func Get(key string) string { return viper.GetString(key) } -// Get int value for key +// GetInt int value for key func GetInt(key string) int { return viper.GetInt(key) } -// Get bool value for key +// GetBool bool value for key func GetBool(key string) bool { return viper.GetBool(key) } @@ -246,8 +248,8 @@ func BasicTest() error { case GenOAuth.Provider != Providers.Google && GenOAuth.AuthURL == "": // everyone except IndieAuth and Google has an authURL return errors.New("configuration error: oauth.auth_url not found") - case GenOAuth.Provider != Providers.Google && GenOAuth.Provider != Providers.IndieAuth && GenOAuth.UserInfoURL == "": - // everyone except IndieAuth and Google has an userInfoURL + case GenOAuth.Provider != Providers.Google && GenOAuth.Provider != Providers.IndieAuth && GenOAuth.Provider != Providers.ADFS && GenOAuth.UserInfoURL == "": + // everyone except IndieAuth, Google and ADFS has an userInfoURL return errors.New("configuration error: oauth.user_info_url not found") } diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index 62ab2481..af99ff65 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -47,6 +47,22 @@ func (u *GoogleUser) PrepareUserData() { u.Username = u.Email } +type ADFSUser struct { + User + Sub string `json:"sub"` + UPN string `json:"upn"` + // UniqueName string `json:"unique_name"` + // PwdExp string `json:"pwd_exp"` + // SID string `json:"sid"` + // Groups string `json:"groups"` + // jwt.StandardClaims +} + +// PrepareUserData implement PersonalData interface +func (u *ADFSUser) PrepareUserData() { + u.Username = u.UPN +} + // GitHubUser is a retrieved and authentiacted user from GitHub. type GitHubUser struct { User