diff --git a/drone/daemon.go b/drone/daemon.go index f2cdf3dd3..bb8e0d898 100644 --- a/drone/daemon.go +++ b/drone/daemon.go @@ -334,16 +334,6 @@ func start(c *cli.Context) error { ) } -func setupConfig(c *cli.Context) *server.Config { - return &server.Config{ - Open: c.Bool("open"), - Yaml: c.String("yaml"), - Secret: c.String("agent-secret"), - Admins: c.StringSlice("admin"), - Orgs: c.StringSlice("orgs"), - } -} - func setupCache(c *cli.Context) cache.Cache { return cache.NewTTL( c.Duration("cache-ttl"), @@ -444,6 +434,24 @@ func setupGithub(c *cli.Context) (remote.Remote, error) { ) } +func setupConfig(c *cli.Context) *server.Config { + return &server.Config{ + Open: c.Bool("open"), + Yaml: c.String("yaml"), + Secret: c.String("agent-secret"), + Admins: sliceToMap(c.StringSlice("admin")), + Orgs: sliceToMap(c.StringSlice("orgs")), + } +} + +func sliceToMap(s []string) map[string]bool { + v := map[string]bool{} + for _, ss := range s { + v[ss] = true + } + return v +} + func printSecret(c *cli.Context) error { secret := c.String("agent-secret") if secret == "" { diff --git a/server/handler.go b/server/handler.go index 25bb1fd5d..fa7b14499 100644 --- a/server/handler.go +++ b/server/handler.go @@ -77,3 +77,9 @@ func HandlerAgent(v string) gin.HandlerFunc { c.Set(k, v) } } + +// ToConfig returns the config from the Context +func ToConfig(c *gin.Context) *Config { + v := c.MustGet("config") + return v.(*Config) +} diff --git a/server/login.go b/server/login.go new file mode 100644 index 000000000..bfd1d2d67 --- /dev/null +++ b/server/login.go @@ -0,0 +1,161 @@ +package server + +import ( + "net/http" + "time" + + "github.com/drone/drone/model" + "github.com/drone/drone/remote" + "github.com/drone/drone/shared/crypto" + "github.com/drone/drone/shared/httputil" + "github.com/drone/drone/shared/token" + "github.com/drone/drone/store" + + "github.com/Sirupsen/logrus" + "github.com/gin-gonic/gin" +) + +func GetLogin(c *gin.Context) { + + // when dealing with redirects we may need to adjust the content type. I + // cannot, however, remember why, so need to revisit this line. + c.Writer.Header().Del("Content-Type") + + tmpuser, err := remote.Login(c, c.Writer, c.Request) + if err != nil { + logrus.Errorf("cannot authenticate user. %s", err) + c.Redirect(303, "/login?error=oauth_error") + return + } + // this will happen when the user is redirected by the remote provider as + // part of the authorization workflow. + if tmpuser == nil { + return + } + config := ToConfig(c) + + // get the user from the database + u, err := store.GetUserLogin(c, tmpuser.Login) + if err != nil { + + // if self-registration is disabled we should return a not authorized error + if !config.Open { + logrus.Errorf("cannot register %s. registration closed", tmpuser.Login) + c.Redirect(303, "/login?error=access_denied") + return + } + + // create the user account + u = &model.User{ + Login: tmpuser.Login, + Token: tmpuser.Token, + Secret: tmpuser.Secret, + Email: tmpuser.Email, + Avatar: tmpuser.Avatar, + Hash: crypto.Rand(), + } + + // insert the user into the database + if err := store.CreateUser(c, u); err != nil { + logrus.Errorf("cannot insert %s. %s", u.Login, err) + c.Redirect(303, "/login?error=internal_error") + return + } + } + + // update the user meta data and authorization data. + u.Token = tmpuser.Token + u.Secret = tmpuser.Secret + u.Email = tmpuser.Email + u.Avatar = tmpuser.Avatar + + if err := store.UpdateUser(c, u); err != nil { + logrus.Errorf("cannot update %s. %s", u.Login, err) + c.Redirect(303, "/login?error=internal_error") + return + } + + if len(config.Orgs) != 0 { + teams, terr := remote.Teams(c, u) + if terr != nil { + logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr) + c.Redirect(303, "/login?error=access_denied") + return + } + var member bool + for _, team := range teams { + if config.Orgs[team.Login] { + member = true + break + } + } + if !member { + logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr) + c.Redirect(303, "/login?error=access_denied") + return + } + } + + exp := time.Now().Add(time.Hour * 72).Unix() + token := token.New(token.SessToken, u.Login) + tokenstr, err := token.SignExpires(u.Hash, exp) + if err != nil { + logrus.Errorf("cannot create token for %s. %s", u.Login, err) + c.Redirect(303, "/login?error=internal_error") + return + } + + httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr) + redirect := httputil.GetCookie(c.Request, "user_last") + if len(redirect) == 0 { + redirect = "/" + } + c.Redirect(303, redirect) + +} + +func GetLogout(c *gin.Context) { + httputil.DelCookie(c.Writer, c.Request, "user_sess") + httputil.DelCookie(c.Writer, c.Request, "user_last") + c.Redirect(303, "/login") +} + +func GetLoginToken(c *gin.Context) { + in := &tokenPayload{} + err := c.Bind(in) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + login, err := remote.Auth(c, in.Access, in.Refresh) + if err != nil { + c.AbortWithError(http.StatusUnauthorized, err) + return + } + + user, err := store.GetUserLogin(c, login) + if err != nil { + c.AbortWithError(http.StatusNotFound, err) + return + } + + exp := time.Now().Add(time.Hour * 72).Unix() + token := token.New(token.SessToken, user.Login) + tokenstr, err := token.SignExpires(user.Hash, exp) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.IndentedJSON(http.StatusOK, &tokenPayload{ + Access: tokenstr, + Expires: exp - time.Now().Unix(), + }) +} + +type tokenPayload struct { + Access string `json:"access_token,omitempty"` + Refresh string `json:"refresh_token,omitempty"` + Expires int64 `json:"expires_in,omitempty"` +} diff --git a/server/server.go b/server/server.go index 557b4b756..0a3109793 100644 --- a/server/server.go +++ b/server/server.go @@ -27,11 +27,11 @@ import ( // Config defines system configuration parameters. type Config struct { - Open bool // Enables open registration - Yaml string // Customize the Yaml configuration file name - Secret string // Secret token used to authenticate agents - Admins []string // Administrative users - Orgs []string // Organization whitelist + Open bool // Enables open registration + Yaml string // Customize the Yaml configuration file name + Secret string // Secret token used to authenticate agents + Admins map[string]bool // Administrative users + Orgs map[string]bool // Organization whitelist } // Server defines the server configuration. @@ -75,7 +75,7 @@ func (s *Server) Handler() http.Handler { e.GET("/repos", web.ShowAllRepos) e.GET("/login", web.ShowLogin) e.GET("/login/form", web.ShowLoginForm) - e.GET("/logout", web.GetLogout) + e.GET("/logout", GetLogout) // TODO below will Go away with React UI settings := e.Group("/settings") @@ -172,9 +172,9 @@ func (s *Server) Handler() http.Handler { auth := e.Group("/authorize") { - auth.GET("", web.GetLogin) - auth.POST("", web.GetLogin) - auth.POST("/token", web.GetLoginToken) + auth.GET("", GetLogin) + auth.POST("", GetLogin) + auth.POST("/token", GetLoginToken) } queue := e.Group("/api/queue") diff --git a/web/login.go b/web/login.go index 4bc03c130..53e7ab8e1 100644 --- a/web/login.go +++ b/web/login.go @@ -1,148 +1,149 @@ package web -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - - log "github.com/Sirupsen/logrus" - "github.com/drone/drone/model" - "github.com/drone/drone/remote" - "github.com/drone/drone/shared/crypto" - "github.com/drone/drone/shared/httputil" - "github.com/drone/drone/shared/token" - "github.com/drone/drone/store" -) - -func GetLogin(c *gin.Context) { - remote := remote.FromContext(c) - - // when dealing with redirects we may need - // to adjust the content type. I cannot, however, - // remember why, so need to revisit this line. - c.Writer.Header().Del("Content-Type") - - tmpuser, err := remote.Login(c.Writer, c.Request) - if err != nil { - log.Errorf("cannot authenticate user. %s", err) - c.Redirect(303, "/login?error=oauth_error") - return - } - // this will happen when the user is redirected by - // the remote provide as part of the oauth dance. - if tmpuser == nil { - return - } - - var open = false // TODO get this from context - - // get the user from the database - u, err := store.GetUserLogin(c, tmpuser.Login) - if err != nil { - - // if self-registration is disabled we should - // return a notAuthorized error. the only exception - // is if no users exist yet in the system we'll proceed. - if !open { - log.Errorf("cannot register %s. registration closed", tmpuser.Login) - c.Redirect(303, "/login?error=access_denied") - return - } - - // create the user account - u = &model.User{} - u.Login = tmpuser.Login - u.Token = tmpuser.Token - u.Secret = tmpuser.Secret - u.Email = tmpuser.Email - u.Avatar = tmpuser.Avatar - u.Hash = crypto.Rand() - - // insert the user into the database - if err := store.CreateUser(c, u); err != nil { - log.Errorf("cannot insert %s. %s", u.Login, err) - c.Redirect(303, "/login?error=internal_error") - return - } - } - - // update the user meta data and authorization - // data and cache in the datastore. - u.Token = tmpuser.Token - u.Secret = tmpuser.Secret - u.Email = tmpuser.Email - u.Avatar = tmpuser.Avatar - - if err := store.UpdateUser(c, u); err != nil { - log.Errorf("cannot update %s. %s", u.Login, err) - c.Redirect(303, "/login?error=internal_error") - return - } - - exp := time.Now().Add(time.Hour * 72).Unix() - token := token.New(token.SessToken, u.Login) - tokenstr, err := token.SignExpires(u.Hash, exp) - if err != nil { - log.Errorf("cannot create token for %s. %s", u.Login, err) - c.Redirect(303, "/login?error=internal_error") - return - } - - httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr) - redirect := httputil.GetCookie(c.Request, "user_last") - if len(redirect) == 0 { - redirect = "/" - } - c.Redirect(303, redirect) - -} - -func GetLogout(c *gin.Context) { - - httputil.DelCookie(c.Writer, c.Request, "user_sess") - httputil.DelCookie(c.Writer, c.Request, "user_last") - c.Redirect(303, "/login") -} - -func GetLoginToken(c *gin.Context) { - remote := remote.FromContext(c) - - in := &tokenPayload{} - err := c.Bind(in) - if err != nil { - c.AbortWithError(http.StatusBadRequest, err) - return - } - - login, err := remote.Auth(in.Access, in.Refresh) - if err != nil { - c.AbortWithError(http.StatusUnauthorized, err) - return - } - - user, err := store.GetUserLogin(c, login) - if err != nil { - c.AbortWithError(http.StatusNotFound, err) - return - } - - exp := time.Now().Add(time.Hour * 72).Unix() - token := token.New(token.SessToken, user.Login) - tokenstr, err := token.SignExpires(user.Hash, exp) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - c.IndentedJSON(http.StatusOK, &tokenPayload{ - Access: tokenstr, - Expires: exp - time.Now().Unix(), - }) -} - -type tokenPayload struct { - Access string `json:"access_token,omitempty"` - Refresh string `json:"refresh_token,omitempty"` - Expires int64 `json:"expires_in,omitempty"` -} +// +// import ( +// "net/http" +// "time" +// +// "github.com/drone/drone/model" +// "github.com/drone/drone/remote" +// "github.com/drone/drone/shared/crypto" +// "github.com/drone/drone/shared/httputil" +// "github.com/drone/drone/shared/token" +// "github.com/drone/drone/store" +// +// "github.com/Sirupsen/logrus" +// "github.com/gin-gonic/gin" +// ) +// +// func GetLogin(c *gin.Context) { +// remote := remote.FromContext(c) +// +// // when dealing with redirects we may need +// // to adjust the content type. I cannot, however, +// // remember why, so need to revisit this line. +// c.Writer.Header().Del("Content-Type") +// +// tmpuser, err := remote.Login(c.Writer, c.Request) +// if err != nil { +// logrus.Errorf("cannot authenticate user. %s", err) +// c.Redirect(303, "/login?error=oauth_error") +// return +// } +// // this will happen when the user is redirected by +// // the remote provide as part of the oauth dance. +// if tmpuser == nil { +// return +// } +// +// var open = false // TODO get this from context +// +// // get the user from the database +// u, err := store.GetUserLogin(c, tmpuser.Login) +// if err != nil { +// +// // if self-registration is disabled we should +// // return a notAuthorized error. the only exception +// // is if no users exist yet in the system we'll proceed. +// if !open { +// logrus.Errorf("cannot register %s. registration closed", tmpuser.Login) +// c.Redirect(303, "/login?error=access_denied") +// return +// } +// +// // create the user account +// u = &model.User{} +// u.Login = tmpuser.Login +// u.Token = tmpuser.Token +// u.Secret = tmpuser.Secret +// u.Email = tmpuser.Email +// u.Avatar = tmpuser.Avatar +// u.Hash = crypto.Rand() +// +// // insert the user into the database +// if err := store.CreateUser(c, u); err != nil { +// logrus.Errorf("cannot insert %s. %s", u.Login, err) +// c.Redirect(303, "/login?error=internal_error") +// return +// } +// } +// +// // update the user meta data and authorization +// // data and cache in the datastore. +// u.Token = tmpuser.Token +// u.Secret = tmpuser.Secret +// u.Email = tmpuser.Email +// u.Avatar = tmpuser.Avatar +// +// if err := store.UpdateUser(c, u); err != nil { +// logrus.Errorf("cannot update %s. %s", u.Login, err) +// c.Redirect(303, "/login?error=internal_error") +// return +// } +// +// exp := time.Now().Add(time.Hour * 72).Unix() +// token := token.New(token.SessToken, u.Login) +// tokenstr, err := token.SignExpires(u.Hash, exp) +// if err != nil { +// logrus.Errorf("cannot create token for %s. %s", u.Login, err) +// c.Redirect(303, "/login?error=internal_error") +// return +// } +// +// httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr) +// redirect := httputil.GetCookie(c.Request, "user_last") +// if len(redirect) == 0 { +// redirect = "/" +// } +// c.Redirect(303, redirect) +// +// } +// +// func GetLogout(c *gin.Context) { +// +// httputil.DelCookie(c.Writer, c.Request, "user_sess") +// httputil.DelCookie(c.Writer, c.Request, "user_last") +// c.Redirect(303, "/login") +// } +// +// func GetLoginToken(c *gin.Context) { +// remote := remote.FromContext(c) +// +// in := &tokenPayload{} +// err := c.Bind(in) +// if err != nil { +// c.AbortWithError(http.StatusBadRequest, err) +// return +// } +// +// login, err := remote.Auth(in.Access, in.Refresh) +// if err != nil { +// c.AbortWithError(http.StatusUnauthorized, err) +// return +// } +// +// user, err := store.GetUserLogin(c, login) +// if err != nil { +// c.AbortWithError(http.StatusNotFound, err) +// return +// } +// +// exp := time.Now().Add(time.Hour * 72).Unix() +// token := token.New(token.SessToken, user.Login) +// tokenstr, err := token.SignExpires(user.Hash, exp) +// if err != nil { +// c.AbortWithError(http.StatusInternalServerError, err) +// return +// } +// +// c.IndentedJSON(http.StatusOK, &tokenPayload{ +// Access: tokenstr, +// Expires: exp - time.Now().Unix(), +// }) +// } +// +// type tokenPayload struct { +// Access string `json:"access_token,omitempty"` +// Refresh string `json:"refresh_token,omitempty"` +// Expires int64 `json:"expires_in,omitempty"` +// }