2015-04-08 22:43:59 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
2015-04-13 05:32:32 +00:00
|
|
|
"time"
|
2015-04-08 22:43:59 +00:00
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2015-04-16 07:31:17 +00:00
|
|
|
"github.com/ungerik/go-gravatar"
|
2015-04-08 22:43:59 +00:00
|
|
|
|
2015-04-11 22:46:30 +00:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2015-04-08 22:43:59 +00:00
|
|
|
"github.com/drone/drone/common"
|
|
|
|
"github.com/drone/drone/common/httputil"
|
|
|
|
"github.com/drone/drone/common/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GetLogin accepts a request to authorize the user and to
|
|
|
|
// return a valid OAuth2 access token. The access token is
|
|
|
|
// returned as url segment #access_token
|
|
|
|
//
|
|
|
|
// GET /authorize
|
|
|
|
//
|
|
|
|
func GetLogin(c *gin.Context) {
|
|
|
|
settings := ToSettings(c)
|
|
|
|
session := ToSession(c)
|
2015-04-15 07:20:00 +00:00
|
|
|
remote := ToRemote(c)
|
2015-04-08 22:43:59 +00:00
|
|
|
store := ToDatastore(c)
|
|
|
|
|
|
|
|
// when dealing with redirects we may need
|
|
|
|
// to adjust the content type. I cannot, however,
|
|
|
|
// rememver why, so need to revisit this line.
|
|
|
|
c.Writer.Header().Del("Content-Type")
|
|
|
|
|
|
|
|
// depending on the configuration a user may
|
|
|
|
// authenticate with OAuth1, OAuth2 or Basic
|
|
|
|
// Auth (username and password). This will delegate
|
|
|
|
// authorization accordingly.
|
|
|
|
switch {
|
|
|
|
case settings.Service.OAuth == nil:
|
|
|
|
getLoginBasic(c)
|
|
|
|
case settings.Service.OAuth.RequestToken != "":
|
|
|
|
getLoginOauth1(c)
|
|
|
|
default:
|
|
|
|
getLoginOauth2(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// exit if authorization fails
|
|
|
|
// TODO(bradrydzewski) return an error message instead
|
|
|
|
if c.Writer.Status() != 200 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
login := ToUser(c)
|
2015-04-15 07:20:00 +00:00
|
|
|
|
|
|
|
// check organization membership, if applicable
|
|
|
|
if len(settings.Service.Orgs) != 0 {
|
|
|
|
orgs, _ := remote.Orgs(login)
|
|
|
|
if !checkMembership(orgs, settings.Service.Orgs) {
|
|
|
|
c.Redirect(303, "/login#error=access_denied_org")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the user from the database
|
2015-04-15 05:04:38 +00:00
|
|
|
u, err := store.User(login.Login)
|
2015-04-08 22:43:59 +00:00
|
|
|
if err != nil {
|
2015-04-15 05:04:38 +00:00
|
|
|
count, err := store.UserCount()
|
2015-04-11 22:46:30 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("cannot register %s. %s", login.Login, err)
|
|
|
|
c.Redirect(303, "/login#error=internal_error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-08 22:43:59 +00:00
|
|
|
// 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.
|
2015-04-11 22:46:30 +00:00
|
|
|
if !settings.Service.Open && count != 0 {
|
|
|
|
log.Errorf("cannot register %s. registration closed", login.Login)
|
|
|
|
c.Redirect(303, "/login#error=access_denied")
|
|
|
|
return
|
2015-04-08 22:43:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// create the user account
|
|
|
|
u = &common.User{}
|
|
|
|
u.Login = login.Login
|
|
|
|
u.Token = login.Token
|
|
|
|
u.Secret = login.Secret
|
|
|
|
u.Name = login.Name
|
|
|
|
u.Email = login.Email
|
2015-04-16 07:31:17 +00:00
|
|
|
u.Gravatar = gravatar.Hash(u.Email)
|
2015-04-08 22:43:59 +00:00
|
|
|
|
|
|
|
// insert the user into the database
|
2015-04-15 05:04:38 +00:00
|
|
|
if err := store.SetUserNotExists(u); err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("cannot insert %s. %s", login.Login, err)
|
|
|
|
c.Redirect(303, "/login#error=internal_error")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:46:30 +00:00
|
|
|
// if this is the first user, they
|
|
|
|
// should be an admin.
|
|
|
|
if count == 0 {
|
2015-04-08 22:43:59 +00:00
|
|
|
u.Admin = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the user meta data and authorization
|
|
|
|
// data and cache in the datastore.
|
|
|
|
u.Token = login.Token
|
|
|
|
u.Secret = login.Secret
|
|
|
|
u.Name = login.Name
|
|
|
|
u.Email = login.Email
|
2015-04-16 07:31:17 +00:00
|
|
|
u.Gravatar = gravatar.Hash(u.Email)
|
2015-04-08 22:43:59 +00:00
|
|
|
|
2015-04-15 05:04:38 +00:00
|
|
|
if err := store.SetUser(u); err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("cannot update %s. %s", u.Login, err)
|
|
|
|
c.Redirect(303, "/login#error=internal_error")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-13 04:35:16 +00:00
|
|
|
token := &common.Token{
|
2015-04-13 05:32:32 +00:00
|
|
|
Kind: common.TokenSess,
|
|
|
|
Login: u.Login,
|
|
|
|
Issued: time.Now().UTC().Unix(),
|
2015-04-13 04:35:16 +00:00
|
|
|
}
|
2015-04-13 06:15:28 +00:00
|
|
|
tokenstr, err := session.GenerateToken(token)
|
2015-04-08 22:43:59 +00:00
|
|
|
if err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("cannot create token for %s. %s", u.Login, err)
|
|
|
|
c.Redirect(303, "/login#error=internal_error")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
2015-04-13 04:35:16 +00:00
|
|
|
c.Redirect(303, "/#access_token="+tokenstr)
|
2015-04-08 22:43:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// getLoginOauth2 is the default authorization implementation
|
|
|
|
// using the oauth2 protocol.
|
|
|
|
func getLoginOauth2(c *gin.Context) {
|
|
|
|
var settings = ToSettings(c)
|
|
|
|
var remote = ToRemote(c)
|
|
|
|
|
|
|
|
var config = &oauth2.Config{
|
|
|
|
ClientId: settings.Service.OAuth.Client,
|
|
|
|
ClientSecret: settings.Service.OAuth.Secret,
|
|
|
|
Scope: strings.Join(settings.Service.OAuth.Scope, ","),
|
|
|
|
AuthURL: settings.Service.OAuth.Authorize,
|
|
|
|
TokenURL: settings.Service.OAuth.AccessToken,
|
|
|
|
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(c.Request)),
|
|
|
|
//settings.Server.Scheme, settings.Server.Hostname),
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the OAuth code
|
|
|
|
var code = c.Request.FormValue("code")
|
|
|
|
//var state = c.Request.FormValue("state")
|
|
|
|
if len(code) == 0 {
|
2015-04-11 22:46:30 +00:00
|
|
|
// TODO this should be a random number, verified by a cookie
|
2015-04-08 22:43:59 +00:00
|
|
|
c.Redirect(303, config.AuthCodeURL("random"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// exhange for a token
|
|
|
|
var trans = &oauth2.Transport{Config: config}
|
|
|
|
var token, err = trans.Exchange(code)
|
|
|
|
if err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("cannot get access_token. %s", err)
|
|
|
|
c.Redirect(303, "/login#error=token_exchange")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// get user account
|
|
|
|
user, err := remote.Login(token.AccessToken, token.RefreshToken)
|
|
|
|
if err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("cannot get user with access_token. %s", err)
|
|
|
|
c.Redirect(303, "/login#error=user_not_found")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the user to the request
|
|
|
|
c.Set("user", user)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getLoginOauth1 is able to authorize a user with the oauth1
|
|
|
|
// authentication protocol. This is used primarily with Bitbucket
|
|
|
|
// and Stash only, and one day I hope can be removed.
|
|
|
|
func getLoginOauth1(c *gin.Context) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// getLoginBasic is able to authorize a user with a username and
|
|
|
|
// password. This can be used for systems that do not support oauth.
|
|
|
|
func getLoginBasic(c *gin.Context) {
|
|
|
|
var (
|
|
|
|
remote = ToRemote(c)
|
|
|
|
username = c.Request.FormValue("username")
|
2015-04-11 05:22:55 +00:00
|
|
|
password = c.Request.FormValue("password")
|
2015-04-08 22:43:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// get user account
|
|
|
|
user, err := remote.Login(username, password)
|
|
|
|
if err != nil {
|
2015-04-11 22:46:30 +00:00
|
|
|
log.Errorf("invalid username or password for %s. %s", username, err)
|
|
|
|
c.Redirect(303, "/login#error=invalid_credentials")
|
2015-04-08 22:43:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the user to the request
|
|
|
|
c.Set("user", user)
|
|
|
|
}
|
2015-04-15 07:20:00 +00:00
|
|
|
|
|
|
|
// checkMembership is a helper function that compares the user's
|
|
|
|
// organization list to a whitelist of organizations that are
|
|
|
|
// approved to use the system.
|
|
|
|
func checkMembership(orgs, whitelist []string) bool {
|
|
|
|
orgs_ := make(map[string]struct{}, len(orgs))
|
|
|
|
for _, org := range orgs {
|
|
|
|
orgs_[org] = struct{}{}
|
|
|
|
}
|
|
|
|
for _, org := range whitelist {
|
|
|
|
if _, ok := orgs_[org]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|