Commit 1c5afa73 by Dan Cech

shared library for managing external user accounts

parent 1594ceeb
......@@ -101,13 +101,13 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
return Error(401, "Login is disabled", nil)
}
authQuery := login.LoginUserQuery{
authQuery := m.LoginUserQuery{
Username: cmd.User,
Password: cmd.Password,
IpAddress: c.Req.RemoteAddr,
}
if err := bus.Dispatch(&authQuery); err != nil {
if err := login.AuthenticateUser(c, &authQuery); err != nil {
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
return Error(401, "Invalid username or password", err)
}
......
......@@ -6,7 +6,6 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
......@@ -14,24 +13,16 @@ import (
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social"
)
var (
ErrProviderDeniedRequest = errors.New("Login provider denied login request")
ErrEmailNotAllowed = errors.New("Required email domain not fulfilled")
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrNoEmail = errors.New("Login provider didn't return an email address")
oauthLogger = log.New("oauth")
)
var oauthLogger = log.New("oauth")
func GenStateString() string {
rnd := make([]byte, 32)
......@@ -56,7 +47,7 @@ func OAuthLogin(ctx *m.ReqContext) {
if errorParam != "" {
errorDesc := ctx.Query("error_description")
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
redirectWithError(ctx, ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
redirectWithError(ctx, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
return
}
......@@ -149,54 +140,42 @@ func OAuthLogin(ctx *m.ReqContext) {
// validate that we got at least an email address
if userInfo.Email == "" {
redirectWithError(ctx, ErrNoEmail)
redirectWithError(ctx, login.ErrNoEmail)
return
}
// validate that the email is allowed to login to grafana
if !connect.IsEmailAllowed(userInfo.Email) {
redirectWithError(ctx, ErrEmailNotAllowed)
redirectWithError(ctx, login.ErrEmailNotAllowed)
return
}
userQuery := m.GetUserByEmailQuery{Email: userInfo.Email}
err = bus.Dispatch(&userQuery)
// create account if missing
if err == m.ErrUserNotFound {
if !connect.IsSignupAllowed() {
redirectWithError(ctx, ErrSignUpNotAllowed)
return
}
limitReached, err := quota.QuotaReached(ctx, "user")
if err != nil {
ctx.Handle(500, "Failed to get user quota", err)
return
}
if limitReached {
redirectWithError(ctx, ErrUsersQuotaReached)
return
}
cmd := m.CreateUserCommand{
Login: userInfo.Login,
Email: userInfo.Email,
Name: userInfo.Name,
Company: userInfo.Company,
DefaultOrgRole: userInfo.Role,
}
extUser := m.ExternalUserInfo{
AuthModule: "oauth_" + name,
AuthId: userInfo.Id,
Name: userInfo.Name,
Login: userInfo.Login,
Email: userInfo.Email,
OrgRoles: map[int64]m.RoleType{},
}
if err = bus.Dispatch(&cmd); err != nil {
ctx.Handle(500, "Failed to create account", err)
return
}
if userInfo.Role != "" {
extUser.OrgRoles[1] = m.RoleType(userInfo.Role)
}
userQuery.Result = &cmd.Result
} else if err != nil {
ctx.Handle(500, "Unexpected error", err)
// add/update user in grafana
userQuery := &m.UpsertUserCommand{
ExternalUser: &extUser,
SignupAllowed: connect.IsSignupAllowed(),
}
err = login.UpsertUser(ctx, userQuery)
if err != nil {
redirectWithError(ctx, err)
return
}
// login
loginUserWithUser(userQuery.Result, ctx)
loginUserWithUser(userQuery.User, ctx)
metrics.M_Api_Login_OAuth.Inc()
......
......@@ -8,23 +8,22 @@ import (
)
var (
ErrInvalidCredentials = errors.New("Invalid Username or Password")
ErrTooManyLoginAttempts = errors.New("Too many consecutive incorrect login attempts for user. Login for user temporarily blocked")
ErrEmailNotAllowed = errors.New("Required email domain not fulfilled")
ErrInvalidCredentials = errors.New("Invalid Username or Password")
ErrNoEmail = errors.New("Login provider didn't return an email address")
ErrProviderDeniedRequest = errors.New("Login provider denied login request")
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrTooManyLoginAttempts = errors.New("Too many consecutive incorrect login attempts for user. Login for user temporarily blocked")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrGettingUserQuota = errors.New("Error getting user quota")
)
type LoginUserQuery struct {
Username string
Password string
User *m.User
IpAddress string
}
func Init() {
bus.AddHandler("auth", AuthenticateUser)
loadLdapConfig()
}
func AuthenticateUser(query *LoginUserQuery) error {
func AuthenticateUser(ctx *m.ReqContext, query *m.LoginUserQuery) error {
if err := validateLoginAttempts(query.Username); err != nil {
return err
}
......@@ -34,7 +33,7 @@ func AuthenticateUser(query *LoginUserQuery) error {
return err
}
ldapEnabled, ldapErr := loginUsingLdap(query)
ldapEnabled, ldapErr := loginUsingLdap(ctx, query)
if ldapEnabled {
if ldapErr == nil || ldapErr != ErrInvalidCredentials {
return ldapErr
......
......@@ -151,7 +151,7 @@ func TestAuthenticateUser(t *testing.T) {
}
type authScenarioContext struct {
loginUserQuery *LoginUserQuery
loginUserQuery *m.LoginUserQuery
grafanaLoginWasCalled bool
ldapLoginWasCalled bool
loginAttemptValidationWasCalled bool
......@@ -161,14 +161,14 @@ type authScenarioContext struct {
type authScenarioFunc func(sc *authScenarioContext)
func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
loginUsingGrafanaDB = func(query *LoginUserQuery) error {
loginUsingGrafanaDB = func(query *m.LoginUserQuery) error {
sc.grafanaLoginWasCalled = true
return err
}
}
func mockLoginUsingLdap(enabled bool, err error, sc *authScenarioContext) {
loginUsingLdap = func(query *LoginUserQuery) (bool, error) {
loginUsingLdap = func(query *m.LoginUserQuery) (bool, error) {
sc.ldapLoginWasCalled = true
return enabled, err
}
......@@ -182,7 +182,7 @@ func mockLoginAttemptValidation(err error, sc *authScenarioContext) {
}
func mockSaveInvalidLoginAttempt(sc *authScenarioContext) {
saveInvalidLoginAttempt = func(query *LoginUserQuery) {
saveInvalidLoginAttempt = func(query *m.LoginUserQuery) {
sc.saveInvalidLoginAttemptWasCalled = true
}
}
......@@ -195,7 +195,7 @@ func authScenario(desc string, fn authScenarioFunc) {
origSaveInvalidLoginAttempt := saveInvalidLoginAttempt
sc := &authScenarioContext{
loginUserQuery: &LoginUserQuery{
loginUserQuery: &m.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
......
......@@ -34,7 +34,7 @@ var validateLoginAttempts = func(username string) error {
return nil
}
var saveInvalidLoginAttempt = func(query *LoginUserQuery) {
var saveInvalidLoginAttempt = func(query *m.LoginUserQuery) {
if setting.DisableBruteForceLoginProtection {
return
}
......
......@@ -50,7 +50,7 @@ func TestLoginAttemptsValidation(t *testing.T) {
return nil
})
saveInvalidLoginAttempt(&LoginUserQuery{
saveInvalidLoginAttempt(&m.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
......@@ -103,7 +103,7 @@ func TestLoginAttemptsValidation(t *testing.T) {
return nil
})
saveInvalidLoginAttempt(&LoginUserQuery{
saveInvalidLoginAttempt(&m.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
......
package login
import (
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/quota"
)
func UpsertUser(ctx *m.ReqContext, cmd *m.UpsertUserCommand) error {
extUser := cmd.ExternalUser
userQuery := m.GetUserByAuthInfoQuery{
AuthModule: extUser.AuthModule,
AuthId: extUser.AuthId,
UserId: extUser.UserId,
Email: extUser.Email,
Login: extUser.Login,
}
err := bus.Dispatch(&userQuery)
if err != nil {
if err != m.ErrUserNotFound {
return err
}
if !cmd.SignupAllowed {
log.Warn(fmt.Sprintf("Not allowing %s login, user not found in internal user database and allow signup = false", extUser.AuthModule))
return ErrInvalidCredentials
}
limitReached, err := quota.QuotaReached(ctx, "user")
if err != nil {
log.Warn("Error getting user quota", "err", err)
return ErrGettingUserQuota
}
if limitReached {
return ErrUsersQuotaReached
}
cmd.User, err = createUser(extUser)
if err != nil {
return err
}
} else {
cmd.User = userQuery.User
// sync user info
err = updateUser(cmd.User, extUser)
if err != nil {
return err
}
}
err = syncOrgRoles(cmd.User, extUser)
if err != nil {
return err
}
return nil
}
func createUser(extUser *m.ExternalUserInfo) (*m.User, error) {
cmd := m.CreateUserCommand{
Login: extUser.Login,
Email: extUser.Email,
Name: extUser.Name,
}
if err := bus.Dispatch(&cmd); err != nil {
return nil, err
}
cmd2 := m.SetAuthInfoCommand{
UserId: cmd.Result.Id,
AuthModule: extUser.AuthModule,
AuthId: extUser.AuthId,
}
if err := bus.Dispatch(&cmd2); err != nil {
return nil, err
}
return &cmd.Result, nil
}
func updateUser(user *m.User, extUser *m.ExternalUserInfo) error {
// sync user info
if user.Login != extUser.Login || user.Email != extUser.Email || user.Name != extUser.Name {
log.Debug("Syncing user info", "id", user.Id, "login", extUser.Login, "email", extUser.Email)
updateCmd := m.UpdateUserCommand{
UserId: user.Id,
Login: extUser.Login,
Email: extUser.Email,
Name: extUser.Name,
}
err := bus.Dispatch(&updateCmd)
if err != nil {
return err
}
}
return nil
}
func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error {
if len(extUser.OrgRoles) == 0 {
// log.Warn("No group mappings defined")
return nil
}
orgsQuery := m.GetUserOrgListQuery{UserId: user.Id}
if err := bus.Dispatch(&orgsQuery); err != nil {
return err
}
handledOrgIds := map[int64]bool{}
deleteOrgIds := []int64{}
// update existing org roles
for _, org := range orgsQuery.Result {
handledOrgIds[org.OrgId] = true
if extUser.OrgRoles[org.OrgId] == "" {
deleteOrgIds = append(deleteOrgIds, org.OrgId)
} else if extUser.OrgRoles[org.OrgId] != org.Role {
// update role
cmd := m.UpdateOrgUserCommand{OrgId: org.OrgId, UserId: user.Id, Role: extUser.OrgRoles[org.OrgId]}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
}
}
// add any new org roles
for orgId, orgRole := range extUser.OrgRoles {
if _, exists := handledOrgIds[orgId]; exists {
continue
}
// add role
cmd := m.AddOrgUserCommand{UserId: user.Id, Role: orgRole, OrgId: orgId}
err := bus.Dispatch(&cmd)
if err != nil && err != m.ErrOrgNotFound {
return err
}
}
// delete any removed org roles
for _, orgId := range deleteOrgIds {
cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: user.Id}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
}
return nil
}
......@@ -17,7 +17,7 @@ var validatePassword = func(providedPassword string, userPassword string, userSa
return nil
}
var loginUsingGrafanaDB = func(query *LoginUserQuery) error {
var loginUsingGrafanaDB = func(query *m.LoginUserQuery) error {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
if err := bus.Dispatch(&userQuery); err != nil {
......
......@@ -66,7 +66,7 @@ func TestGrafanaLogin(t *testing.T) {
}
type grafanaLoginScenarioContext struct {
loginUserQuery *LoginUserQuery
loginUserQuery *m.LoginUserQuery
validatePasswordCalled bool
}
......@@ -77,7 +77,7 @@ func grafanaLoginScenario(desc string, fn grafanaLoginScenarioFunc) {
origValidatePassword := validatePassword
sc := &grafanaLoginScenarioContext{
loginUserQuery: &LoginUserQuery{
loginUserQuery: &m.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
......
......@@ -10,7 +10,6 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
......@@ -24,10 +23,9 @@ type ILdapConn interface {
}
type ILdapAuther interface {
Login(query *LoginUserQuery) error
SyncSignedInUser(signedInUser *m.SignedInUser) error
GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error)
SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error
Login(ctx *m.ReqContext, query *m.LoginUserQuery) error
SyncSignedInUser(ctx *m.ReqContext, signedInUser *m.SignedInUser) error
GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo) (*m.User, error)
}
type ldapAuther struct {
......@@ -89,89 +87,36 @@ func (a *ldapAuther) Dial() error {
return err
}
func (a *ldapAuther) Login(query *LoginUserQuery) error {
if err := a.Dial(); err != nil {
func (a *ldapAuther) Login(ctx *m.ReqContext, query *m.LoginUserQuery) error {
// connect to ldap server
err := a.Dial()
if err != nil {
return err
}
defer a.conn.Close()
// perform initial authentication
if err := a.initialBind(query.Username, query.Password); err != nil {
err = a.initialBind(query.Username, query.Password)
if err != nil {
return err
}
// find user entry & attributes
if ldapUser, err := a.searchForUser(query.Username); err != nil {
return err
} else {
a.log.Debug("Ldap User found", "info", spew.Sdump(ldapUser))
// check if a second user bind is needed
if a.requireSecondBind {
if err := a.secondBind(ldapUser, query.Password); err != nil {
return err
}
}
if grafanaUser, err := a.GetGrafanaUserFor(ldapUser); err != nil {
return err
} else {
if syncErr := a.syncInfoAndOrgRoles(grafanaUser, ldapUser); syncErr != nil {
return syncErr
}
query.User = grafanaUser
return nil
}
}
}
func (a *ldapAuther) SyncSignedInUser(signedInUser *m.SignedInUser) error {
grafanaUser := m.User{
Id: signedInUser.UserId,
Login: signedInUser.Login,
Email: signedInUser.Email,
Name: signedInUser.Name,
}
if err := a.Dial(); err != nil {
ldapUser, err := a.searchForUser(query.Username)
if err != nil {
return err
}
defer a.conn.Close()
if err := a.serverBind(); err != nil {
return err
}
a.log.Debug("Ldap User found", "info", spew.Sdump(ldapUser))
if ldapUser, err := a.searchForUser(signedInUser.Login); err != nil {
a.log.Error("Failed searching for user in ldap", "error", err)
return err
} else {
if err := a.syncInfoAndOrgRoles(&grafanaUser, ldapUser); err != nil {
// check if a second user bind is needed
if a.requireSecondBind {
err = a.secondBind(ldapUser, query.Password)
if err != nil {
return err
}
a.log.Debug("Got Ldap User Info", "user", spew.Sdump(ldapUser))
}
return nil
}
// Sync info for ldap user and grafana user
func (a *ldapAuther) syncInfoAndOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
// sync user details
if err := a.syncUserInfo(user, ldapUser); err != nil {
return err
}
// sync org roles
if err := a.SyncOrgRoles(user, ldapUser); err != nil {
return err
}
return nil
}
func (a *ldapAuther) GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error) {
// validate that the user has access
// if there are no ldap group mappings access is true
// otherwise a single group must match
......@@ -184,123 +129,85 @@ func (a *ldapAuther) GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error)
}
if !access {
a.log.Info("Ldap Auth: user does not belong in any of the specified ldap groups", "username", ldapUser.Username, "groups", ldapUser.MemberOf)
return nil, ErrInvalidCredentials
a.log.Info(
"Ldap Auth: user does not belong in any of the specified ldap groups",
"username", ldapUser.Username,
"groups", ldapUser.MemberOf)
return ErrInvalidCredentials
}
// get user from grafana db
userQuery := m.GetUserByLoginQuery{LoginOrEmail: ldapUser.Username}
if err := bus.Dispatch(&userQuery); err != nil {
if err == m.ErrUserNotFound && setting.LdapAllowSignup {
return a.createGrafanaUser(ldapUser)
} else if err == m.ErrUserNotFound {
a.log.Warn("Not allowing LDAP login, user not found in internal user database, and ldap allow signup = false")
return nil, ErrInvalidCredentials
} else {
return nil, err
}
grafanaUser, err := a.GetGrafanaUserFor(ctx, ldapUser)
if err != nil {
return err
}
return userQuery.Result, nil
query.User = grafanaUser
return nil
}
func (a *ldapAuther) createGrafanaUser(ldapUser *LdapUserInfo) (*m.User, error) {
cmd := m.CreateUserCommand{
Login: ldapUser.Username,
Email: ldapUser.Email,
Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName),
}
if err := bus.Dispatch(&cmd); err != nil {
return nil, err
func (a *ldapAuther) SyncSignedInUser(ctx *m.ReqContext, signedInUser *m.SignedInUser) error {
err := a.Dial()
if err != nil {
return err
}
return &cmd.Result, nil
}
defer a.conn.Close()
func (a *ldapAuther) syncUserInfo(user *m.User, ldapUser *LdapUserInfo) error {
var name = fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName)
if user.Email == ldapUser.Email && user.Name == name {
return nil
err = a.serverBind()
if err != nil {
return err
}
a.log.Debug("Syncing user info", "username", ldapUser.Username)
updateCmd := m.UpdateUserCommand{}
updateCmd.UserId = user.Id
updateCmd.Login = user.Login
updateCmd.Email = ldapUser.Email
updateCmd.Name = fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName)
return bus.Dispatch(&updateCmd)
}
func (a *ldapAuther) SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
if len(a.server.LdapGroups) == 0 {
a.log.Warn("No group mappings defined")
return nil
ldapUser, err := a.searchForUser(signedInUser.Login)
if err != nil {
a.log.Error("Failed searching for user in ldap", "error", err)
return err
}
orgsQuery := m.GetUserOrgListQuery{UserId: user.Id}
if err := bus.Dispatch(&orgsQuery); err != nil {
grafanaUser, err := a.GetGrafanaUserFor(ctx, ldapUser)
if err != nil {
return err
}
handledOrgIds := map[int64]bool{}
signedInUser.Login = grafanaUser.Login
signedInUser.Email = grafanaUser.Email
signedInUser.Name = grafanaUser.Name
// update or remove org roles
for _, org := range orgsQuery.Result {
match := false
handledOrgIds[org.OrgId] = true
for _, group := range a.server.LdapGroups {
if org.OrgId != group.OrgId {
continue
}
if ldapUser.isMemberOf(group.GroupDN) {
match = true
if org.Role != group.OrgRole {
// update role
cmd := m.UpdateOrgUserCommand{OrgId: org.OrgId, UserId: user.Id, Role: group.OrgRole}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
}
// ignore subsequent ldap group mapping matches
break
}
}
return nil
}
// remove role if no mappings match
if !match {
cmd := m.RemoveOrgUserCommand{OrgId: org.OrgId, UserId: user.Id}
if err := bus.Dispatch(&cmd); err != nil {
return err
}
}
func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo) (*m.User, error) {
extUser := m.ExternalUserInfo{
AuthModule: "ldap",
AuthId: ldapUser.DN,
Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName),
Login: ldapUser.Username,
Email: ldapUser.Email,
OrgRoles: map[int64]m.RoleType{},
}
// add missing org roles
for _, group := range a.server.LdapGroups {
if !ldapUser.isMemberOf(group.GroupDN) {
// only use the first match for each org
if extUser.OrgRoles[group.OrgId] != "" {
continue
}
if _, exists := handledOrgIds[group.OrgId]; exists {
continue
}
// add role
cmd := m.AddOrgUserCommand{UserId: user.Id, Role: group.OrgRole, OrgId: group.OrgId}
err := bus.Dispatch(&cmd)
if err != nil && err != m.ErrOrgNotFound {
return err
if ldapUser.isMemberOf(group.GroupDN) {
extUser.OrgRoles[group.OrgId] = group.OrgRole
}
}
// mark this group has handled so we do not process it again
handledOrgIds[group.OrgId] = true
// add/update user in grafana
userQuery := m.UpsertUserCommand{
ExternalUser: &extUser,
SignupAllowed: setting.LdapAllowSignup,
}
err := UpsertUser(ctx, &userQuery)
if err != nil {
return nil, err
}
return nil
return userQuery.User, nil
}
func (a *ldapAuther) serverBind() error {
......@@ -470,7 +377,3 @@ func getLdapAttrArray(name string, result *ldap.SearchResult) []string {
}
return []string{}
}
func createUserFromLdapInfo() error {
return nil
}
package login
import (
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
var loginUsingLdap = func(query *LoginUserQuery) (bool, error) {
var loginUsingLdap = func(ctx *m.ReqContext, query *m.LoginUserQuery) (bool, error) {
if !setting.LdapEnabled {
return false, nil
}
for _, server := range LdapCfg.Servers {
author := NewLdapAuthenticator(server)
err := author.Login(query)
err := author.Login(ctx, query)
if err == nil || err != ErrInvalidCredentials {
return true, err
}
......
......@@ -79,7 +79,7 @@ func TestLdapLogin(t *testing.T) {
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
sc.withLoginResult(false)
enabled, err := loginUsingLdap(&LoginUserQuery{
enabled, err := loginUsingLdap(&m.LoginUserQuery{
Username: "user",
Password: "pwd",
})
......@@ -117,7 +117,7 @@ type mockLdapAuther struct {
loginCalled bool
}
func (a *mockLdapAuther) Login(query *LoginUserQuery) error {
func (a *mockLdapAuther) Login(query *m.LoginUserQuery) error {
a.loginCalled = true
if !a.validLogin {
......@@ -140,7 +140,7 @@ func (a *mockLdapAuther) SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) erro
}
type ldapLoginScenarioContext struct {
loginUserQuery *LoginUserQuery
loginUserQuery *m.LoginUserQuery
ldapAuthenticatorMock *mockLdapAuther
}
......@@ -151,7 +151,7 @@ func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) {
origNewLdapAuthenticator := NewLdapAuthenticator
sc := &ldapLoginScenarioContext{
loginUserQuery: &LoginUserQuery{
loginUserQuery: &m.LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
......
......@@ -112,7 +112,7 @@ var syncGrafanaUserWithLdapUser = func(ctx *m.ReqContext, query *m.GetSignedInUs
for _, server := range ldapCfg.Servers {
author := login.NewLdapAuthenticator(server)
if err := author.SyncSignedInUser(query.Result); err != nil {
if err := author.SyncSignedInUser(ctx, query.Result); err != nil {
return err
}
}
......
......@@ -116,7 +116,7 @@ type mockLdapAuthenticator struct {
syncSignedInUserCalled bool
}
func (a *mockLdapAuthenticator) Login(query *login.LoginUserQuery) error {
func (a *mockLdapAuthenticator) Login(query *m.LoginUserQuery) error {
return nil
}
......
......@@ -8,7 +8,6 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/log"
l "github.com/grafana/grafana/pkg/login"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
......@@ -165,7 +164,7 @@ func initContextWithBasicAuth(ctx *m.ReqContext, orgId int64) bool {
user := loginQuery.Result
loginUserQuery := l.LoginUserQuery{Username: username, Password: password, User: user}
loginUserQuery := m.LoginUserQuery{Username: username, Password: password, User: user}
if err := bus.Dispatch(&loginUserQuery); err != nil {
ctx.JsonApiErr(401, "Invalid username or password", err)
return true
......
......@@ -9,7 +9,6 @@ import (
ms "github.com/go-macaron/session"
"github.com/grafana/grafana/pkg/bus"
l "github.com/grafana/grafana/pkg/login"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
......@@ -72,7 +71,7 @@ func TestMiddlewareContext(t *testing.T) {
return nil
})
bus.AddHandler("test", func(loginUserQuery *l.LoginUserQuery) error {
bus.AddHandler("test", func(loginUserQuery *m.LoginUserQuery) error {
return nil
})
......
package models
type UserAuth struct {
Id int64
UserId int64
AuthModule string
AuthId string
}
type ExternalUserInfo struct {
AuthModule string
AuthId string
UserId int64
Email string
Login string
Name string
OrgRoles map[int64]RoleType
}
// ---------------------
// COMMANDS
type UpsertUserCommand struct {
ExternalUser *ExternalUserInfo
SignupAllowed bool
User *User
}
type SetAuthInfoCommand struct {
AuthModule string
AuthId string
UserId int64
}
type DeleteAuthInfoCommand struct {
UserAuth *UserAuth
}
// ----------------------
// QUERIES
type LoginUserQuery struct {
Username string
Password string
User *User
IpAddress string
}
type GetUserByAuthInfoQuery struct {
AuthModule string
AuthId string
UserId int64
Email string
Login string
User *User
UserAuth *UserAuth
}
type GetAuthInfoQuery struct {
AuthModule string
AuthId string
UserAuth *UserAuth
}
......@@ -30,6 +30,7 @@ func AddMigrations(mg *Migrator) {
addDashboardAclMigrations(mg)
addTagMigration(mg)
addLoginAttemptMigrations(mg)
addUserAuthMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
......
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addUserAuthMigrations(mg *Migrator) {
userAuthV1 := Table{
Name: "user_auth",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "user_id", Type: DB_BigInt, Nullable: false},
{Name: "auth_module", Type: DB_NVarchar, Length: 30, Nullable: false},
{Name: "auth_id", Type: DB_NVarchar, Length: 100, Nullable: false},
{Name: "created", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"auth_module", "auth_id"}},
},
}
// create table
mg.AddMigration("create user auth table", NewAddTableMigration(userAuthV1))
// add indices
addTableIndicesMigrations(mg, "v1", userAuthV1)
}
......@@ -445,6 +445,7 @@ func DeleteUser(cmd *m.DeleteUserCommand) error {
"DELETE FROM dashboard_acl WHERE user_id = ?",
"DELETE FROM preferences WHERE user_id = ?",
"DELETE FROM team_member WHERE user_id = ?",
"DELETE FROM user_auth WHERE user_id = ?",
}
for _, sql := range deletes {
......
package sqlstore
import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetUserByAuthInfo)
bus.AddHandler("sql", GetAuthInfo)
bus.AddHandler("sql", SetAuthInfo)
bus.AddHandler("sql", DeleteAuthInfo)
}
func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error {
user := new(m.User)
has := false
var err error
// Try to find the user by auth module and id first
if query.AuthModule != "" && query.AuthId != "" {
authQuery := &m.GetAuthInfoQuery{
AuthModule: query.AuthModule,
AuthId: query.AuthId,
}
err = GetAuthInfo(authQuery)
// if user id was specified and doesn't match the user_auth entry, remove it
if err == nil && query.UserId != 0 && query.UserId != authQuery.UserAuth.UserId {
DeleteAuthInfo(&m.DeleteAuthInfoCommand{
UserAuth: authQuery.UserAuth,
})
} else if err == nil {
has, err = x.Id(authQuery.UserAuth.UserId).Get(user)
if err != nil {
return err
}
if has {
query.UserAuth = authQuery.UserAuth
} else {
// if the user has been deleted then remove the entry
DeleteAuthInfo(&m.DeleteAuthInfoCommand{
UserAuth: authQuery.UserAuth,
})
}
} else if err != m.ErrUserNotFound {
return err
}
}
// If not found, try to find the user by id
if !has && query.UserId != 0 {
has, err = x.Id(query.UserId).Get(user)
if err != nil {
return err
}
}
// If not found, try to find the user by email address
if !has && query.Email != "" {
user = &m.User{Email: query.Email}
has, err = x.Get(user)
if err != nil {
return err
}
}
// If not found, try to find the user by login
if !has && query.Login != "" {
user = &m.User{Login: query.Login}
has, err = x.Get(user)
if err != nil {
return err
}
}
// No user found
if !has {
return m.ErrUserNotFound
}
query.User = user
return nil
}
func GetAuthInfo(query *m.GetAuthInfoQuery) error {
userAuth := &m.UserAuth{
AuthModule: query.AuthModule,
AuthId: query.AuthId,
}
has, err := x.Get(userAuth)
if err != nil {
return err
}
if !has {
return m.ErrUserNotFound
}
query.UserAuth = userAuth
return nil
}
func SetAuthInfo(cmd *m.SetAuthInfoCommand) error {
return inTransaction(func(sess *DBSession) error {
authUser := m.UserAuth{
UserId: cmd.UserId,
AuthModule: cmd.AuthModule,
AuthId: cmd.AuthId,
}
_, err := sess.Insert(&authUser)
if err != nil {
return err
}
return nil
})
}
func DeleteAuthInfo(cmd *m.DeleteAuthInfoCommand) error {
return inTransaction(func(sess *DBSession) error {
_, err := sess.Delete(cmd.UserAuth)
if err != nil {
return err
}
return nil
})
}
......@@ -51,6 +51,7 @@ func (s *SocialGrafanaCom) IsOrganizationMember(organizations []OrgRecord) bool
func (s *SocialGrafanaCom) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
Name string `json:"name"`
Login string `json:"username"`
Email string `json:"email"`
......@@ -69,6 +70,7 @@ func (s *SocialGrafanaCom) UserInfo(client *http.Client, token *oauth2.Token) (*
}
userInfo := &BasicUserInfo{
Id: fmt.Sprintf("%d", data.Id),
Name: data.Name,
Login: data.Login,
Email: data.Email,
......
......@@ -14,6 +14,7 @@ import (
)
type BasicUserInfo struct {
Id string
Name string
Email string
Login string
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment