Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for ADFS #5

Merged
merged 1 commit into from
Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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