diff options
-rw-r--r-- | api/user.go | 61 | ||||
-rw-r--r-- | config/config.json | 12 | ||||
-rw-r--r-- | model/user.go | 4 | ||||
-rw-r--r-- | utils/config.go | 14 | ||||
-rw-r--r-- | web/web.go | 108 |
5 files changed, 141 insertions, 58 deletions
diff --git a/api/user.go b/api/user.go index 4765a5611..a9c8a0065 100644 --- a/api/user.go +++ b/api/user.go @@ -20,6 +20,7 @@ import ( _ "image/gif" _ "image/jpeg" "image/png" + "io" "net/http" "net/url" "strconv" @@ -1222,51 +1223,85 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) { } } -func AuthorizeGitLabUser(code, state, uri string) (*model.GitLabUser, *model.AppError) { - if !model.ComparePassword(state, utils.Cfg.SSOSettings.GitLabId) { - return nil, model.NewAppError("AuthorizeGitLabUser", "Invalid state", "") +func GetAuthorizationCode(c *Context, w http.ResponseWriter, r *http.Request, service, redirectUri string) { + + teamId := r.FormValue("id") + + if len(teamId) != 26 { + c.Err = model.NewAppError("GetAuthorizationCode", "Invalid team id", "team_id="+teamId) + c.Err.StatusCode = http.StatusBadRequest + return + } + + // Make sure team exists + if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { + c.Err = result.Err + return + } + + if s, ok := utils.Cfg.SSOSettings[service]; !ok || !s.Allow { + c.Err = model.NewAppError("GetAuthorizationCode", "Unsupported OAuth service provider", "service="+service) + c.Err.StatusCode = http.StatusBadRequest + return + } + + clientId := utils.Cfg.SSOSettings[service].Id + endpoint := utils.Cfg.SSOSettings[service].AuthEndpoint + state := model.HashPassword(clientId) + + authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri+"?id="+teamId) + "&state=" + url.QueryEscape(state) + http.Redirect(w, r, authUrl, http.StatusFound) +} + +func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.AppError) { + if s, ok := utils.Cfg.SSOSettings[service]; !ok || !s.Allow { + return nil, model.NewAppError("AuthorizeOAuthUser", "Unsupported OAuth service provider", "service="+service) + } + + if !model.ComparePassword(state, utils.Cfg.SSOSettings[service].Id) { + return nil, model.NewAppError("AuthorizeOAuthUser", "Invalid state", "") } p := url.Values{} - p.Set("client_id", utils.Cfg.SSOSettings.GitLabId) - p.Set("client_secret", utils.Cfg.SSOSettings.GitLabSecret) + p.Set("client_id", utils.Cfg.SSOSettings[service].Id) + p.Set("client_secret", utils.Cfg.SSOSettings[service].Secret) p.Set("code", code) p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - p.Set("redirect_uri", uri) + p.Set("redirect_uri", redirectUri) client := &http.Client{} - req, _ := http.NewRequest("POST", utils.Cfg.SSOSettings.GitLabUrl+"/oauth/token", strings.NewReader(p.Encode())) + req, _ := http.NewRequest("POST", utils.Cfg.SSOSettings[service].TokenEndpoint, strings.NewReader(p.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") var ar *model.AccessResponse if resp, err := client.Do(req); err != nil { - return nil, model.NewAppError("AuthorizeGitLabUser", "Token request to GitLab failed", err.Error()) + return nil, model.NewAppError("AuthorizeOAuthUser", "Token request to GitLab failed", err.Error()) } else { ar = model.AccessResponseFromJson(resp.Body) } if ar.TokenType != model.ACCESS_TOKEN_TYPE { - return nil, model.NewAppError("AuthorizeGitLabUser", "Bad token type", "token_type="+ar.TokenType) + return nil, model.NewAppError("AuthorizeOAuthUser", "Bad token type", "token_type="+ar.TokenType) } if len(ar.AccessToken) == 0 { - return nil, model.NewAppError("AuthorizeGitLabUser", "Missing access token", "") + return nil, model.NewAppError("AuthorizeOAuthUser", "Missing access token", "") } p = url.Values{} p.Set("access_token", ar.AccessToken) - req, _ = http.NewRequest("GET", utils.Cfg.SSOSettings.GitLabUrl+"/api/v3/user", strings.NewReader("")) + req, _ = http.NewRequest("GET", utils.Cfg.SSOSettings[service].UserApiEndpoint, strings.NewReader("")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+ar.AccessToken) if resp, err := client.Do(req); err != nil { - return nil, model.NewAppError("AuthorizeGitLabUser", "Token request to GitLab failed", err.Error()) + return nil, model.NewAppError("AuthorizeOAuthUser", "Token request to "+service+" failed", err.Error()) } else { - return model.GitLabUserFromJson(resp.Body), nil + return resp.Body, nil } } diff --git a/config/config.json b/config/config.json index 61183948f..f92b873d1 100644 --- a/config/config.json +++ b/config/config.json @@ -24,10 +24,14 @@ "StorageDirectory": "./data/" }, "SSOSettings": { - "AllowGitLabSSO": true, - "GitLabSecret" : "8526ada64f38a1a67cafe6650d54310f1484f8a5d06ad23abb9f8e4b8af1c429", - "GitLabId": "0af4138195d246d5d4e958a93100379066bb087fa9892cd323b0c97bbd696008", - "GitLabUrl": "http://dockerhost:8080" + "gitlab": { + "Allow": true, + "Secret" : "8526ada64f38a1a67cafe6650d54310f1484f8a5d06ad23abb9f8e4b8af1c429", + "Id": "0af4138195d246d5d4e958a93100379066bb087fa9892cd323b0c97bbd696008", + "AuthEndpoint": "http://dockerhost:8080/oauth/authorize", + "TokenEndpoint": "http://dockerhost:8080/oauth/token", + "UserApiEndpoint": "http://dockerhost:8080/api/v3/user" + } }, "SqlSettings": { "DriverName": "mysql", diff --git a/model/user.go b/model/user.go index 22158b6ac..78b033327 100644 --- a/model/user.go +++ b/model/user.go @@ -385,3 +385,7 @@ func GitLabUserFromJson(data io.Reader) *GitLabUser { return nil } } + +func (glu *GitLabUser) GetAuthData() string { + return strconv.FormatInt(glu.Id, 10) +} diff --git a/utils/config.go b/utils/config.go index 163c912bf..b90337b7e 100644 --- a/utils/config.go +++ b/utils/config.go @@ -32,11 +32,13 @@ type ServiceSettings struct { StorageDirectory string } -type SSOSettings struct { - AllowGitLabSSO bool - GitLabSecret string - GitLabId string - GitLabUrl string +type SSOSetting struct { + Allow bool + Secret string + Id string + AuthEndpoint string + TokenEndpoint string + UserApiEndpoint string } type SqlSettings struct { @@ -116,7 +118,7 @@ type Config struct { EmailSettings EmailSettings PrivacySettings PrivacySettings TeamSettings TeamSettings - SSOSettings SSOSettings + SSOSettings map[string]SSOSetting } func (o *Config) ToJson() string { diff --git a/web/web.go b/web/web.go index 85901a8d2..71cf87335 100644 --- a/web/web.go +++ b/web/web.go @@ -14,7 +14,6 @@ import ( "gopkg.in/fsnotify.v1" "html/template" "net/http" - "net/url" "strconv" "strings" ) @@ -54,8 +53,8 @@ func InitWeb() { mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/", api.AppHandler(login)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET") - mainrouter.Handle("/login/gitlab", api.AppHandlerIndependent(loginWithGitLab)).Methods("GET") - mainrouter.Handle("/login/gitlab/complete", api.AppHandlerIndependent(loginCompleteGitLab)).Methods("GET") + mainrouter.Handle("/login/{service:[A-Za-z]+}", api.AppHandlerIndependent(loginWithOAuth)).Methods("GET") + mainrouter.Handle("/login/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(loginCompleteOAuth)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET") @@ -67,8 +66,8 @@ func InitWeb() { mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET") mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).Methods("GET") - mainrouter.Handle("/signup/gitlab", api.AppHandlerIndependent(signupWithGitLab)).Methods("GET") - mainrouter.Handle("/signup/gitlab/complete", api.AppHandlerIndependent(signupCompleteGitLab)).Methods("GET") + mainrouter.Handle("/signup/{service:[A-Za-z]+}", api.AppHandlerIndependent(signupWithOAuth)).Methods("GET") + mainrouter.Handle("/signup/{service:[A-Za-z]+}/complete", api.AppHandlerIndependent(signupCompleteOAuth)).Methods("GET") mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET") mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET") @@ -449,33 +448,52 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) { page.Render(c, w) } -func signupWithGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) { - teamId := r.FormValue("id") - - if len(teamId) != 26 { - c.Err = model.NewAppError("signupWithGitLab", "Invalid team id", "team_id="+teamId) - return - } +func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + service := params["service"] - state := model.HashPassword(utils.Cfg.SSOSettings.GitLabId) + redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete" - authUrl := utils.Cfg.SSOSettings.GitLabUrl + "/oauth/authorize" - authUrl += "?response_type=code&client_id=" + utils.Cfg.SSOSettings.GitLabId + "&redirect_uri=" + url.QueryEscape("http://localhost:8065/signup/gitlab/complete?id="+teamId) + "&state=" + url.QueryEscape(state) - http.Redirect(w, r, authUrl, http.StatusFound) + api.GetAuthorizationCode(c, w, r, service, redirectUri) } -func signupCompleteGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) { +func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + service := params["service"] + code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") teamId := r.FormValue("id") - uri := "http://localhost:8065/signup/gitlab/complete?id=" + teamId + uri := c.GetSiteURL() + "/signup/" + service + "/complete?id=" + teamId + + if len(teamId) != 26 { + c.Err = model.NewAppError("signupCompleteOAuth", "Invalid team id", "team_id="+teamId) + c.Err.StatusCode = http.StatusBadRequest + return + } + + // Make sure team exists + if result := <-api.Srv.Store.Team().Get(teamId); result.Err != nil { + c.Err = result.Err + return + } - if glu, err := api.AuthorizeGitLabUser(code, state, uri); err != nil { + if body, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil { c.Err = err return } else { - user := model.UserFromGitLabUser(glu) + var user *model.User + if service == model.USER_AUTH_SERVICE_GITLAB { + glu := model.GitLabUserFromJson(body) + user = model.UserFromGitLabUser(glu) + } + + if user == nil { + c.Err = model.NewAppError("signupCompleteOAuth", "Could not create user out of "+service+" user object", "") + return + } + user.TeamId = teamId page := NewHtmlTemplatePage("signup_user_oauth", "Complete User Sign Up") @@ -484,34 +502,54 @@ func signupCompleteGitLab(c *api.Context, w http.ResponseWriter, r *http.Request } } -func loginWithGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) { - teamId := r.FormValue("id") - - if len(teamId) != 26 { - c.Err = model.NewAppError("signupWithGitLab", "Invalid team id", "team_id="+teamId) - return - } +func loginWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + service := params["service"] - state := model.HashPassword(utils.Cfg.SSOSettings.GitLabId) + redirectUri := c.GetSiteURL() + "/login/" + service + "/complete" - authUrl := utils.Cfg.SSOSettings.GitLabUrl + "/oauth/authorize" - authUrl += "?response_type=code&client_id=" + utils.Cfg.SSOSettings.GitLabId + "&redirect_uri=" + url.QueryEscape("http://localhost:8065/login/gitlab/complete?id="+teamId) + "&state=" + url.QueryEscape(state) - http.Redirect(w, r, authUrl, http.StatusFound) + api.GetAuthorizationCode(c, w, r, service, redirectUri) } -func loginCompleteGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) { +func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + service := params["service"] + code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") teamId := r.FormValue("id") - uri := "http://localhost:8065/login/gitlab/complete?id=" + teamId + uri := c.GetSiteURL() + "/login/" + service + "/complete?id=" + teamId - if glu, err := api.AuthorizeGitLabUser(code, state, uri); err != nil { + if len(teamId) != 26 { + c.Err = model.NewAppError("loginCompleteOAuth", "Invalid team id", "team_id="+teamId) + c.Err.StatusCode = http.StatusBadRequest + return + } + + // Make sure team exists + if result := <-api.Srv.Store.Team().Get(teamId); result.Err != nil { + c.Err = result.Err + return + } + + if body, err := api.AuthorizeOAuthUser(service, code, state, uri); err != nil { c.Err = err return } else { + authData := "" + if service == model.USER_AUTH_SERVICE_GITLAB { + glu := model.GitLabUserFromJson(body) + authData = glu.GetAuthData() + } + + if len(authData) == 0 { + c.Err = model.NewAppError("loginCompleteOAuth", "Could not parse auth data out of "+service+" user object", "") + return + } + var user *model.User - if result := <-api.Srv.Store.User().GetByAuth(teamId, strconv.FormatInt(glu.Id, 10), model.USER_AUTH_SERVICE_GITLAB); result.Err != nil { + if result := <-api.Srv.Store.User().GetByAuth(teamId, authData, service); result.Err != nil { c.Err = result.Err return } else { |