Skip to content

Commit

Permalink
Add initial support for ADFS (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
simongottschlag authored Feb 6, 2019
1 parent 957647b commit 2dfcccd
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 4 deletions.
73 changes: 73 additions & 0 deletions handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"

log "github.com/Sirupsen/logrus"
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 6 additions & 4 deletions pkg/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type OAuthProviders struct {
Google string
GitHub string
IndieAuth string
ADFS string
OIDC string
}

Expand Down Expand Up @@ -113,6 +114,7 @@ var (
Google: "google",
GitHub: "github",
IndieAuth: "indieauth",
ADFS: "adfs",
OIDC: "oidc",
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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")
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2dfcccd

Please sign in to comment.