mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-18 16:01:05 +00:00
210 lines
5.5 KiB
Go
210 lines
5.5 KiB
Go
package server
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
|
|
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
|
|
|
|
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
|
|
common "github.com/drone/drone/pkg/types"
|
|
)
|
|
|
|
// 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)
|
|
remote := ToRemote(c)
|
|
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.Auth == nil:
|
|
// getLoginBasic(c)
|
|
case settings.Auth.RequestToken != "":
|
|
getLoginOauth1(c)
|
|
default:
|
|
getLoginOauth2(c)
|
|
}
|
|
|
|
// exit if authorization fails
|
|
if c.Writer.Status() != 200 {
|
|
return
|
|
}
|
|
|
|
login := ToUser(c)
|
|
|
|
// check organization membership, if applicable
|
|
if len(remote.GetOrgs()) != 0 {
|
|
orgs, _ := remote.Orgs(login)
|
|
if !checkMembership(orgs, remote.GetOrgs()) {
|
|
c.Redirect(303, "/login#error=access_denied_org")
|
|
return
|
|
}
|
|
}
|
|
|
|
// get the user from the database
|
|
u, err := store.UserLogin(login.Login)
|
|
if err != nil {
|
|
count, err := store.UserCount()
|
|
if err != nil {
|
|
log.Errorf("cannot register %s. %s", login.Login, err)
|
|
c.Redirect(303, "/login#error=internal_error")
|
|
return
|
|
}
|
|
|
|
// 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 !remote.GetOpen() && count != 0 {
|
|
log.Errorf("cannot register %s. registration closed", login.Login)
|
|
c.Redirect(303, "/login#error=access_denied")
|
|
return
|
|
}
|
|
|
|
// create the user account
|
|
u = &common.User{}
|
|
u.Login = login.Login
|
|
u.Token = login.Token
|
|
u.Secret = login.Secret
|
|
u.Email = login.Email
|
|
u.Avatar = gravatar.Hash(u.Email)
|
|
|
|
// insert the user into the database
|
|
if err := store.AddUser(u); err != nil {
|
|
log.Errorf("cannot insert %s. %s", login.Login, err)
|
|
c.Redirect(303, "/login#error=internal_error")
|
|
return
|
|
}
|
|
|
|
// if this is the first user, they
|
|
// should be an admin.
|
|
if count == 0 {
|
|
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.Email = login.Email
|
|
u.Avatar = gravatar.Hash(u.Email)
|
|
|
|
if err := store.SetUser(u); err != nil {
|
|
log.Errorf("cannot update %s. %s", u.Login, err)
|
|
c.Redirect(303, "/login#error=internal_error")
|
|
return
|
|
}
|
|
|
|
token := &common.Token{
|
|
Kind: common.TokenSess,
|
|
Login: u.Login,
|
|
Issued: time.Now().UTC().Unix(),
|
|
}
|
|
tokenstr, err := session.GenerateToken(token)
|
|
if err != nil {
|
|
log.Errorf("cannot create token for %s. %s", u.Login, err)
|
|
c.Redirect(303, "/login#error=internal_error")
|
|
return
|
|
}
|
|
c.Redirect(303, "/#access_token="+tokenstr)
|
|
}
|
|
|
|
// getLoginOauth2 is the default authorization implementation
|
|
// using the oauth2 protocol.
|
|
func getLoginOauth2(c *gin.Context) {
|
|
var remote = ToRemote(c)
|
|
|
|
// Bugagazavr: I think this must be moved to remote config
|
|
//var scope = strings.Join(settings.Auth.Scope, ",")
|
|
//if scope == "" {
|
|
// scope = remote.Scope()
|
|
//}
|
|
var transport = remote.Oauth2Transport(c.Request)
|
|
|
|
// get the OAuth code
|
|
var code = c.Request.FormValue("code")
|
|
//var state = c.Request.FormValue("state")
|
|
if len(code) == 0 {
|
|
// TODO this should be a random number, verified by a cookie
|
|
c.Redirect(303, transport.AuthCodeURL("random"))
|
|
return
|
|
}
|
|
|
|
// exhange for a token
|
|
var token, err = transport.Exchange(code)
|
|
if err != nil {
|
|
log.Errorf("cannot get access_token. %s", err)
|
|
c.Redirect(303, "/login#error=token_exchange")
|
|
return
|
|
}
|
|
|
|
// get user account
|
|
user, err := remote.Login(token.AccessToken, token.RefreshToken)
|
|
if err != nil {
|
|
log.Errorf("cannot get user with access_token. %s", err)
|
|
c.Redirect(303, "/login#error=user_not_found")
|
|
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")
|
|
password = c.Request.FormValue("password")
|
|
)
|
|
|
|
// get user account
|
|
user, err := remote.Login(username, password)
|
|
if err != nil {
|
|
log.Errorf("invalid username or password for %s. %s", username, err)
|
|
c.Redirect(303, "/login#error=invalid_credentials")
|
|
return
|
|
}
|
|
|
|
// add the user to the request
|
|
c.Set("user", user)
|
|
}
|
|
|
|
// 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
|
|
}
|