inital implementation of using GitLab OAuth2 provider for signup/login

Этот коммит содержится в:
JoramWilander
2015-07-15 12:48:50 -04:00
родитель 2fef71da69
Коммит c39e95c7cb
11 изменённых файлов: 473 добавлений и 68 удалений

Просмотреть файл

@@ -133,6 +133,10 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
user.EmailVerified = true user.EmailVerified = true
} }
if len(user.AuthData) > 0 && len(user.AuthService) > 0 {
user.EmailVerified = true
}
ruser := CreateUser(c, team, user) ruser := CreateUser(c, team, user)
if c.Err != nil { if c.Err != nil {
return return
@@ -233,80 +237,71 @@ func FireAndForgetVerifyEmail(userId, name, email, teamDisplayName, teamURL stri
}() }()
} }
func login(c *Context, w http.ResponseWriter, r *http.Request) { func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, password, deviceId string) {
props := model.MapFromJson(r.Body) if result := <-Srv.Store.User().Get(userId); result.Err != nil {
c.Err = result.Err
extraInfo := ""
var result store.StoreResult
if len(props["id"]) != 0 {
extraInfo = props["id"]
if result = <-Srv.Store.User().Get(props["id"]); result.Err != nil {
c.Err = result.Err
return
}
}
var team *model.Team
if result.Data == nil && len(props["email"]) != 0 && len(props["name"]) != 0 {
extraInfo = props["email"] + " in " + props["name"]
if nr := <-Srv.Store.Team().GetByName(props["name"]); nr.Err != nil {
c.Err = nr.Err
return
} else {
team = nr.Data.(*model.Team)
if result = <-Srv.Store.User().GetByEmail(team.Id, props["email"]); result.Err != nil {
c.Err = result.Err
return
}
}
}
if result.Data == nil {
c.Err = model.NewAppError("login", "Login failed because we couldn't find a valid account", extraInfo)
c.Err.StatusCode = http.StatusBadRequest
return return
} } else {
user := result.Data.(*model.User)
user := result.Data.(*model.User) if checkUserPassword(c, user, password) {
Login(c, w, r, user, deviceId)
if team == nil {
if tResult := <-Srv.Store.Team().Get(user.TeamId); tResult.Err != nil {
c.Err = tResult.Err
return
} else {
team = tResult.Data.(*model.Team)
} }
} }
}
func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, name, password, deviceId string) {
var team *model.Team
if result := <-Srv.Store.Team().GetByName(name); result.Err != nil {
c.Err = result.Err
return
} else {
team = result.Data.(*model.Team)
}
if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil {
c.Err = result.Err
return
} else {
user := result.Data.(*model.User)
if checkUserPassword(c, user, password) {
Login(c, w, r, user, deviceId)
}
}
}
func checkUserPassword(c *Context, user *model.User, password string) bool {
if !model.ComparePassword(user.Password, password) {
c.LogAuditWithUserId(user.Id, "fail")
c.Err = model.NewAppError("checkUserPassword", "Login failed because of invalid password", "user_id="+user.Id)
c.Err.StatusCode = http.StatusForbidden
return false
}
return true
}
// User MUST be validated before calling Login
func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) {
c.LogAuditWithUserId(user.Id, "attempt") c.LogAuditWithUserId(user.Id, "attempt")
if !model.ComparePassword(user.Password, props["password"]) {
c.LogAuditWithUserId(user.Id, "fail")
c.Err = model.NewAppError("login", "Login failed because of invalid password", extraInfo)
c.Err.StatusCode = http.StatusForbidden
return
}
if !user.EmailVerified && !utils.Cfg.EmailSettings.ByPassEmail { if !user.EmailVerified && !utils.Cfg.EmailSettings.ByPassEmail {
c.Err = model.NewAppError("login", "Login failed because email address has not been verified", extraInfo) c.Err = model.NewAppError("Login", "Login failed because email address has not been verified", "user_id="+user.Id)
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
return return
} }
if user.DeleteAt > 0 { if user.DeleteAt > 0 {
c.Err = model.NewAppError("login", "Login failed because your account has been set to inactive. Please contact an administrator.", extraInfo) c.Err = model.NewAppError("Login", "Login failed because your account has been set to inactive. Please contact an administrator.", "user_id="+user.Id)
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
return return
} }
session := &model.Session{UserId: user.Id, TeamId: team.Id, Roles: user.Roles, DeviceId: props["device_id"]} session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId}
maxAge := model.SESSION_TIME_WEB_IN_SECS maxAge := model.SESSION_TIME_WEB_IN_SECS
if len(props["device_id"]) > 0 { if len(deviceId) > 0 {
session.SetExpireInDays(model.SESSION_TIME_MOBILE_IN_DAYS) session.SetExpireInDays(model.SESSION_TIME_MOBILE_IN_DAYS)
maxAge = model.SESSION_TIME_MOBILE_IN_SECS maxAge = model.SESSION_TIME_MOBILE_IN_SECS
} else { } else {
@@ -361,8 +356,22 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
c.Session = *session c.Session = *session
c.LogAuditWithUserId(user.Id, "success") c.LogAuditWithUserId(user.Id, "success")
}
w.Write([]byte(result.Data.(*model.User).ToJson())) func login(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
if len(props["id"]) != 0 {
LoginById(c, w, r, props["id"], props["password"], props["device_id"])
} else if len(props["email"]) != 0 && len(props["name"]) != 0 {
LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["device_id"])
}
if c.Err != nil {
return
}
w.Write([]byte("{}"))
} }
func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -1212,3 +1221,52 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
} }
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", "")
}
p := url.Values{}
p.Set("client_id", utils.Cfg.SSOSettings.GitLabId)
p.Set("client_secret", utils.Cfg.SSOSettings.GitLabSecret)
p.Set("code", code)
p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
p.Set("redirect_uri", uri)
client := &http.Client{}
req, _ := http.NewRequest("POST", utils.Cfg.SSOSettings.GitLabUrl+"/oauth/token", 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())
} 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)
}
if len(ar.AccessToken) == 0 {
return nil, model.NewAppError("AuthorizeGitLabUser", "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.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())
} else {
return model.GitLabUserFromJson(resp.Body), nil
}
}

Просмотреть файл

@@ -23,6 +23,12 @@
"UseLocalStorage": true, "UseLocalStorage": true,
"StorageDirectory": "./data/" "StorageDirectory": "./data/"
}, },
"SSOSettings": {
"AllowGitLabSSO": true,
"GitLabSecret" : "8526ada64f38a1a67cafe6650d54310f1484f8a5d06ad23abb9f8e4b8af1c429",
"GitLabId": "0af4138195d246d5d4e958a93100379066bb087fa9892cd323b0c97bbd696008",
"GitLabUrl": "http://dockerhost:8080"
},
"SqlSettings": { "SqlSettings": {
"DriverName": "mysql", "DriverName": "mysql",
"DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test", "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test",

41
model/access.go Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
ACCESS_TOKEN_TYPE = "bearer"
)
type AccessResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int32 `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
}
func (ar *AccessResponse) ToJson() string {
b, err := json.Marshal(ar)
if err != nil {
return ""
} else {
return string(b)
}
}
func AccessResponseFromJson(data io.Reader) *AccessResponse {
decoder := json.NewDecoder(data)
var ar AccessResponse
err := decoder.Decode(&ar)
if err == nil {
return &ar
} else {
return nil
}
}

Просмотреть файл

@@ -8,22 +8,24 @@ import (
"encoding/json" "encoding/json"
"io" "io"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
const ( const (
ROLE_ADMIN = "admin" ROLE_ADMIN = "admin"
ROLE_SYSTEM_ADMIN = "system_admin" ROLE_SYSTEM_ADMIN = "system_admin"
ROLE_SYSTEM_SUPPORT = "system_support" ROLE_SYSTEM_SUPPORT = "system_support"
USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes
USER_OFFLINE_TIMEOUT = 1 * 60 * 1000 // 1 minute USER_OFFLINE_TIMEOUT = 1 * 60 * 1000 // 1 minute
USER_OFFLINE = "offline" USER_OFFLINE = "offline"
USER_AWAY = "away" USER_AWAY = "away"
USER_ONLINE = "online" USER_ONLINE = "online"
USER_NOTIFY_ALL = "all" USER_NOTIFY_ALL = "all"
USER_NOTIFY_MENTION = "mention" USER_NOTIFY_MENTION = "mention"
USER_NOTIFY_NONE = "none" USER_NOTIFY_NONE = "none"
BOT_USERNAME = "valet" BOT_USERNAME = "valet"
USER_AUTH_SERVICE_GITLAB = "gitlab"
) )
type User struct { type User struct {
@@ -48,6 +50,14 @@ type User struct {
NotifyProps StringMap `json:"notify_props"` NotifyProps StringMap `json:"notify_props"`
LastPasswordUpdate int64 `json:"last_password_update"` LastPasswordUpdate int64 `json:"last_password_update"`
LastPictureUpdate int64 `json:"last_picture_update"` LastPictureUpdate int64 `json:"last_picture_update"`
AuthService string `json:"auth_service"`
}
type GitLabUser struct {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Name string `json:"name"`
} }
// IsValid validates the user and returns an error if it isn't configured // IsValid validates the user and returns an error if it isn't configured
@@ -96,6 +106,22 @@ func (u *User) IsValid() *AppError {
return NewAppError("User.IsValid", "Invalid last name", "user_id="+u.Id) return NewAppError("User.IsValid", "Invalid last name", "user_id="+u.Id)
} }
if len(u.Password) > 128 {
return NewAppError("User.IsValid", "Invalid password", "user_id="+u.Id)
}
if len(u.AuthData) > 128 {
return NewAppError("User.IsValid", "Invalid auth data", "user_id="+u.Id)
}
if len(u.AuthData) > 0 && len(u.AuthService) == 0 {
return NewAppError("User.IsValid", "Invalid user, auth data must be set with auth type", "user_id="+u.Id)
}
if len(u.Password) > 0 && len(u.AuthData) > 0 {
return NewAppError("User.IsValid", "Invalid user, password and auth data cannot both be set", "user_id="+u.Id)
}
return nil return nil
} }
@@ -328,3 +354,34 @@ func IsUsernameValid(username string) bool {
return true return true
} }
func UserFromGitLabUser(glu *GitLabUser) *User {
user := &User{}
user.Username = glu.Username
splitName := strings.Split(glu.Name, " ")
if len(splitName) == 2 {
user.FirstName = splitName[0]
user.LastName = splitName[1]
} else if len(splitName) >= 2 {
user.FirstName = splitName[0]
user.LastName = strings.Join(splitName[1:], " ")
} else {
user.FirstName = glu.Name
}
user.Email = glu.Email
user.AuthData = strconv.FormatInt(glu.Id, 10)
user.AuthService = USER_AUTH_SERVICE_GITLAB
return user
}
func GitLabUserFromJson(data io.Reader) *GitLabUser {
decoder := json.NewDecoder(data)
var glu GitLabUser
err := decoder.Decode(&glu)
if err == nil {
return &glu
} else {
return nil
}
}

Просмотреть файл

@@ -31,8 +31,10 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
table.ColMap("Roles").SetMaxSize(64) table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(4000) table.ColMap("Props").SetMaxSize(4000)
table.ColMap("NotifyProps").SetMaxSize(2000) table.ColMap("NotifyProps").SetMaxSize(2000)
table.ColMap("AuthService").SetMaxSize(32)
table.SetUniqueTogether("Email", "TeamId") table.SetUniqueTogether("Email", "TeamId")
table.SetUniqueTogether("Username", "TeamId") table.SetUniqueTogether("Username", "TeamId")
table.SetUniqueTogether("AuthData", "AuthService", "TeamId")
} }
return us return us
@@ -57,6 +59,8 @@ func (us SqlUserStore) UpgradeSchemaIfNeeded() {
panic("Failed to set last name from nickname " + err.Error()) panic("Failed to set last name from nickname " + err.Error())
} }
} }
us.CreateColumnIfNotExists("Users", "AuthService", "LastPictureUpdate", "varchar(32)", "") // for OAuth Client
} }
//func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, afterName string, colType string, defaultValue string) bool { //func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, afterName string, colType string, defaultValue string) bool {
@@ -369,6 +373,28 @@ func (us SqlUserStore) GetByEmail(teamId string, email string) StoreChannel {
return storeChannel return storeChannel
} }
func (us SqlUserStore) GetByAuth(teamId string, authData string, authService string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
user := model.User{}
if err := us.GetReplica().SelectOne(&user, "SELECT * FROM Users WHERE TeamId=? AND AuthData=? AND AuthService=?", teamId, authData, authService); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetByAuth", "We couldn't find the existing account", "teamId="+teamId+", authData="+authData+", authService="+authService+", "+err.Error())
}
result.Data = &user
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChannel { func (us SqlUserStore) GetByUsername(teamId string, username string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -85,6 +85,7 @@ type UserStore interface {
Get(id string) StoreChannel Get(id string) StoreChannel
GetProfiles(teamId string) StoreChannel GetProfiles(teamId string) StoreChannel
GetByEmail(teamId string, email string) StoreChannel GetByEmail(teamId string, email string) StoreChannel
GetByAuth(teamId string, authData string, authService string) StoreChannel
GetByUsername(teamId string, username string) StoreChannel GetByUsername(teamId string, username string) StoreChannel
VerifyEmail(userId string) StoreChannel VerifyEmail(userId string) StoreChannel
GetEtagForProfiles(teamId string) StoreChannel GetEtagForProfiles(teamId string) StoreChannel

Просмотреть файл

@@ -32,6 +32,13 @@ type ServiceSettings struct {
StorageDirectory string StorageDirectory string
} }
type SSOSettings struct {
AllowGitLabSSO bool
GitLabSecret string
GitLabId string
GitLabUrl string
}
type SqlSettings struct { type SqlSettings struct {
DriverName string DriverName string
DataSource string DataSource string
@@ -109,6 +116,7 @@ type Config struct {
EmailSettings EmailSettings EmailSettings EmailSettings
PrivacySettings PrivacySettings PrivacySettings PrivacySettings
TeamSettings TeamSettings TeamSettings TeamSettings
SSOSettings SSOSettings
} }
func (o *Config) ToJson() string { func (o *Config) ToJson() string {

84
web/react/components/signup_user_oauth.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,84 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
module.exports = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
if (!this.state.user.username) {
this.setState({name_error: "This field is required", email_error: "", password_error: "", server_error: ""});
return;
}
var username_error = utils.isValidUsername(this.state.user.username);
if (username_error === "Cannot use a reserved word as a username.") {
this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""});
return;
} else if (username_error) {
this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.", email_error: "", password_error: "", server_error: ""});
return;
}
this.setState({name_error: "", server_error: ""});
this.state.user.allow_marketing = this.refs.email_service.getDOMNode().checked;
var user = this.state.user;
client.createUser(user, "", "",
function(data) {
client.track('signup', 'signup_user_oauth_02');
window.location.href = '/login/'+user.auth_service+'?id='+user.team_id;
}.bind(this),
function(err) {
this.state.server_error = err.message;
this.setState(this.state);
}.bind(this)
);
},
handleChange: function() {
var user = this.state.user;
user.username = this.refs.name.getDOMNode().value;
this.setState({ user: user });
},
getInitialState: function() {
var user = JSON.parse(this.props.user);
return { user: user };
},
render: function() {
client.track('signup', 'signup_user_oauth_01');
var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null;
var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null;
var yourEmailIs = this.state.user.email == "" ? "" : <span>Your email address is <b>{ this.state.user.email }.</b></span>;
return (
<div>
<img className="signup-team-logo" src="/static/images/logo.png" />
<h4>Welcome to { config.SiteName }</h4>
<p>{"To continue signing up with " + this.state.user.auth_type + ", please register a username."}</p>
<p>Your username can be made of lowercase letters and numbers.</p>
<label className="control-label">Username</label>
<div className={ name_error ? "form-group has-error" : "form-group" }>
<input type="text" ref="name" className="form-control" placeholder="" maxLength="128" value={this.state.user.username} onChange={this.handleChange} />
{ name_error }
</div>
<p>{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others"}</p>
<p>{ yourEmailIs } Youll use this address to sign in to {config.SiteName}.</p>
<div className="checkbox"><label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service. </label></div>
<p><button onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p>
{ server_error }
<p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
</div>
);
}
});

11
web/react/pages/signup_user_oauth.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,11 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var SignupUserOAuth = require('../components/signup_user_oauth.jsx');
global.window.setup_signup_user_oauth_page = function(user) {
React.render(
<SignupUserOAuth user={user} />,
document.getElementById('signup-user-complete')
);
};

26
web/templates/signup_user_oauth.html Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
{{define "signup_user_oauth"}}
<!DOCTYPE html>
<html>
{{template "head" . }}
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
<div class="col-sm-12">
<div class="signup-team__container">
<div id="signup-user-complete"></div>
</div>
</div>
<div class="footer-push"></div>
</div>
<div class="row footer">
{{template "footer" . }}
</div>
</div>
</div>
<script>
window.setup_signup_user_oauth_page('{{.Props.User}}');
</script>
</body>
</html>
{{end}}

Просмотреть файл

@@ -14,6 +14,7 @@ import (
"gopkg.in/fsnotify.v1" "gopkg.in/fsnotify.v1"
"html/template" "html/template"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
) )
@@ -52,6 +53,10 @@ 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-]+}", api.AppHandler(login)).Methods("GET")
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-]+}/", api.AppHandler(login)).Methods("GET")
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", 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("/{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-]+}/logout", api.AppHandler(logout)).Methods("GET")
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET")
// Bug in gorilla.mux pervents us from using regex here. // Bug in gorilla.mux pervents us from using regex here.
@@ -61,6 +66,10 @@ func InitWeb() {
mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET") mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET")
mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET") mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET")
mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).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("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET") mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET")
mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET") mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET")
mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET") mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET")
@@ -439,3 +448,81 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
page.Props["IsReset"] = strconv.FormatBool(isResetLink) page.Props["IsReset"] = strconv.FormatBool(isResetLink)
page.Render(c, w) 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
}
state := model.HashPassword(utils.Cfg.SSOSettings.GitLabId)
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)
}
func signupCompleteGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) {
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
if glu, err := api.AuthorizeGitLabUser(code, state, uri); err != nil {
c.Err = err
return
} else {
user := model.UserFromGitLabUser(glu)
user.TeamId = teamId
page := NewHtmlTemplatePage("signup_user_oauth", "Complete User Sign Up")
page.Props["User"] = user.ToJson()
page.Render(c, w)
}
}
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
}
state := model.HashPassword(utils.Cfg.SSOSettings.GitLabId)
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)
}
func loginCompleteGitLab(c *api.Context, w http.ResponseWriter, r *http.Request) {
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
if glu, err := api.AuthorizeGitLabUser(code, state, uri); err != nil {
c.Err = err
return
} else {
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 {
c.Err = result.Err
return
} else {
user = result.Data.(*model.User)
api.Login(c, w, r, user, "")
if c.Err != nil {
return
}
root(c, w, r)
}
}
}