2019-09-03 18:34:44 +01:00
package api
import (
2021-11-08 09:53:51 -05:00
"context"
2020-11-19 13:34:28 +01:00
"errors"
2019-09-03 18:34:44 +01:00
"fmt"
"net/http"
2022-01-14 17:55:57 +01:00
"strconv"
2022-04-21 16:38:55 +02:00
"strings"
2019-09-03 18:34:44 +01:00
2021-01-15 14:43:20 +01:00
"github.com/grafana/grafana/pkg/api/response"
2019-09-08 11:48:47 +01:00
"github.com/grafana/grafana/pkg/infra/log"
2019-09-03 18:34:44 +01:00
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
2022-08-10 08:21:33 +00:00
"github.com/grafana/grafana/pkg/services/login"
2019-09-03 18:34:44 +01:00
"github.com/grafana/grafana/pkg/services/multildap"
2022-08-10 11:56:48 +02:00
"github.com/grafana/grafana/pkg/services/org"
2022-07-20 14:50:06 +02:00
"github.com/grafana/grafana/pkg/services/user"
2019-09-03 18:34:44 +01:00
"github.com/grafana/grafana/pkg/util"
2021-10-11 14:30:59 +02:00
"github.com/grafana/grafana/pkg/web"
2019-09-03 18:34:44 +01:00
)
var (
getLDAPConfig = multildap . GetConfig
newLDAP = multildap . New
2020-12-15 09:32:06 +01:00
ldapLogger = log . New ( "LDAP.debug" )
2019-09-08 11:48:47 +01:00
2019-09-03 18:34:44 +01:00
errOrganizationNotFound = func ( orgId int64 ) error {
2020-11-05 13:07:06 +01:00
return fmt . Errorf ( "unable to find organization with ID '%d'" , orgId )
2019-09-03 18:34:44 +01:00
}
)
// LDAPAttribute is a serializer for user attributes mapped from LDAP. Is meant to display both the serialized value and the LDAP key we received it from.
type LDAPAttribute struct {
ConfigAttributeValue string `json:"cfgAttrValue"`
LDAPAttributeValue string `json:"ldapValue"`
}
// RoleDTO is a serializer for mapped roles from LDAP
2019-09-19 16:13:38 +01:00
type LDAPRoleDTO struct {
2022-08-10 11:56:48 +02:00
OrgId int64 `json:"orgId"`
OrgName string `json:"orgName"`
OrgRole org . RoleType `json:"orgRole"`
GroupDN string `json:"groupDN"`
2019-09-03 18:34:44 +01:00
}
// LDAPUserDTO is a serializer for users mapped from LDAP
type LDAPUserDTO struct {
2019-09-08 11:48:47 +01:00
Name * LDAPAttribute `json:"name"`
Surname * LDAPAttribute `json:"surname"`
Email * LDAPAttribute `json:"email"`
Username * LDAPAttribute `json:"login"`
IsGrafanaAdmin * bool `json:"isGrafanaAdmin"`
IsDisabled bool `json:"isDisabled"`
2019-09-19 16:13:38 +01:00
OrgRoles [] LDAPRoleDTO `json:"roles"`
2019-09-08 11:48:47 +01:00
Teams [] models . TeamOrgGroupDTO `json:"teams"`
2019-09-03 18:34:44 +01:00
}
2019-09-13 16:26:25 +01:00
// LDAPServerDTO is a serializer for LDAP server statuses
type LDAPServerDTO struct {
Host string `json:"host"`
Port int `json:"port"`
Available bool `json:"available"`
Error string `json:"error"`
}
2019-09-03 18:34:44 +01:00
// FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
2022-09-20 09:55:40 +02:00
func ( user * LDAPUserDTO ) FetchOrgs ( ctx context . Context , orga org . Service ) error {
2019-09-03 18:34:44 +01:00
orgIds := [] int64 {}
for _ , or := range user . OrgRoles {
orgIds = append ( orgIds , or . OrgId )
}
2022-09-20 09:55:40 +02:00
q := & org . SearchOrgsQuery {}
q . IDs = orgIds
2019-09-03 18:34:44 +01:00
2022-09-20 09:55:40 +02:00
result , err := orga . Search ( ctx , q )
if err != nil {
2019-09-03 18:34:44 +01:00
return err
}
orgNamesById := map [ int64 ] string {}
2022-09-20 09:55:40 +02:00
for _ , org := range result {
orgNamesById [ org . ID ] = org . Name
2019-09-03 18:34:44 +01:00
}
for i , orgDTO := range user . OrgRoles {
2019-09-19 16:13:38 +01:00
if orgDTO . OrgId < 1 {
continue
}
2019-09-03 18:34:44 +01:00
orgName := orgNamesById [ orgDTO . OrgId ]
if orgName != "" {
user . OrgRoles [ i ]. OrgName = orgName
} else {
return errOrganizationNotFound ( orgDTO . OrgId )
}
}
return nil
}
2022-07-27 16:54:37 +03:00
// swagger:route POST /admin/ldap/reload admin_ldap reloadLDAPCfg
//
// Reloads the LDAP configuration.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.config:reload`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2021-11-29 10:18:01 +01:00
func ( hs * HTTPServer ) ReloadLDAPCfg ( c * models . ReqContext ) response . Response {
2019-09-03 18:34:44 +01:00
if ! ldap . IsEnabled () {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "LDAP is not enabled" , nil )
2019-09-03 18:34:44 +01:00
}
err := ldap . ReloadConfig ()
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusInternalServerError , "Failed to reload LDAP config" , err )
2019-09-03 18:34:44 +01:00
}
2021-01-15 14:43:20 +01:00
return response . Success ( "LDAP config reloaded" )
2019-09-03 18:34:44 +01:00
}
2022-07-27 16:54:37 +03:00
// swagger:route GET /admin/ldap/status admin_ldap getLDAPStatus
//
// Attempts to connect to all the configured LDAP servers and returns information on whenever they're available or not.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.status:read`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2021-01-15 14:43:20 +01:00
func ( hs * HTTPServer ) GetLDAPStatus ( c * models . ReqContext ) response . Response {
2019-09-04 15:29:14 +01:00
if ! ldap . IsEnabled () {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "LDAP is not enabled" , nil )
2019-09-04 15:29:14 +01:00
}
2020-12-11 11:44:44 +01:00
ldapConfig , err := getLDAPConfig ( hs . Cfg )
2019-09-04 15:29:14 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Failed to obtain the LDAP configuration. Please verify the configuration and try again" , err )
2019-09-04 15:29:14 +01:00
}
ldap := newLDAP ( ldapConfig . Servers )
2019-09-16 17:56:01 +02:00
if ldap == nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusInternalServerError , "Failed to find the LDAP server" , nil )
2019-09-16 17:56:01 +02:00
}
2019-09-04 15:29:14 +01:00
statuses , err := ldap . Ping ()
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Failed to connect to the LDAP server(s)" , err )
2019-09-04 15:29:14 +01:00
}
serverDTOs := [] * LDAPServerDTO {}
for _ , status := range statuses {
s := & LDAPServerDTO {
Host : status . Host ,
Available : status . Available ,
Port : status . Port ,
}
if status . Error != nil {
s . Error = status . Error . Error ()
}
serverDTOs = append ( serverDTOs , s )
}
2021-01-15 14:43:20 +01:00
return response . JSON ( http . StatusOK , serverDTOs )
2019-09-04 15:29:14 +01:00
}
2022-07-27 16:54:37 +03:00
// swagger:route POST /admin/ldap/sync/{user_id} admin_ldap postSyncUserWithLDAP
//
// Enables a single Grafana user to be synchronized against LDAP.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.user:sync`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2021-01-15 14:43:20 +01:00
func ( hs * HTTPServer ) PostSyncUserWithLDAP ( c * models . ReqContext ) response . Response {
2019-09-13 16:26:25 +01:00
if ! ldap . IsEnabled () {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "LDAP is not enabled" , nil )
2019-09-13 16:26:25 +01:00
}
2020-12-11 11:44:44 +01:00
ldapConfig , err := getLDAPConfig ( hs . Cfg )
2019-09-13 16:26:25 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Failed to obtain the LDAP configuration. Please verify the configuration and try again" , err )
2019-09-13 16:26:25 +01:00
}
2022-01-14 17:55:57 +01:00
userId , err := strconv . ParseInt ( web . Params ( c . Req )[ ":id" ], 10 , 64 )
if err != nil {
return response . Error ( http . StatusBadRequest , "id is invalid" , err )
}
2019-09-13 16:26:25 +01:00
2022-08-02 16:58:05 +02:00
query := user . GetUserByIDQuery { ID : userId }
2019-09-13 16:26:25 +01:00
2022-08-02 16:58:05 +02:00
usr , err := hs . userService . GetByID ( c . Req . Context (), & query )
if err != nil { // validate the userId exists
2022-07-20 14:50:06 +02:00
if errors . Is ( err , user . ErrUserNotFound ) {
return response . Error ( 404 , user . ErrUserNotFound . Error (), nil )
2019-09-13 16:26:25 +01:00
}
2021-01-15 14:43:20 +01:00
return response . Error ( 500 , "Failed to get user" , err )
2019-09-13 16:26:25 +01:00
}
2022-08-10 08:21:33 +00:00
authModuleQuery := & models . GetAuthInfoQuery { UserId : usr . ID , AuthModule : login . LDAPAuthModule }
2022-02-09 18:45:31 +08:00
if err := hs . authInfoService . GetAuthInfo ( c . Req . Context (), authModuleQuery ); err != nil { // validate the userId comes from LDAP
2022-07-20 14:50:06 +02:00
if errors . Is ( err , user . ErrUserNotFound ) {
return response . Error ( 404 , user . ErrUserNotFound . Error (), nil )
2019-09-13 16:26:25 +01:00
}
2021-01-15 14:43:20 +01:00
return response . Error ( 500 , "Failed to get user" , err )
2019-09-13 16:26:25 +01:00
}
ldapServer := newLDAP ( ldapConfig . Servers )
2022-08-02 16:58:05 +02:00
userInfo , _ , err := ldapServer . User ( usr . Login )
2019-09-13 16:26:25 +01:00
if err != nil {
2020-11-19 13:34:28 +01:00
if errors . Is ( err , multildap . ErrDidNotFindUser ) { // User was not in the LDAP server - we need to take action:
2022-08-02 16:58:05 +02:00
if hs . Cfg . AdminUser == usr . Login { // User is *the* Grafana Admin. We cannot disable it.
errMsg := fmt . Sprintf ( `Refusing to sync grafana super admin "%s" - it would be disabled` , usr . Login )
2020-12-15 09:32:06 +01:00
ldapLogger . Error ( errMsg )
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , errMsg , err )
2019-09-13 16:26:25 +01:00
}
// Since the user was not in the LDAP server. Let's disable it.
2022-08-02 16:58:05 +02:00
err := hs . Login . DisableExternalUser ( c . Req . Context (), usr . Login )
2019-09-13 16:26:25 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusInternalServerError , "Failed to disable the user" , err )
2019-09-13 16:26:25 +01:00
}
2020-11-05 15:37:11 +01:00
err = hs . AuthTokenService . RevokeAllUserTokens ( c . Req . Context (), userId )
2019-09-13 16:26:25 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusInternalServerError , "Failed to remove session tokens for the user" , err )
2019-09-13 16:26:25 +01:00
}
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "User not found in LDAP. Disabled the user without updating information" , nil ) // should this be a success?
2019-09-13 16:26:25 +01:00
}
2019-11-07 14:31:44 +01:00
2020-12-15 09:32:06 +01:00
ldapLogger . Debug ( "Failed to sync the user with LDAP" , "err" , err )
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Something went wrong while finding the user in LDAP" , err )
2019-09-13 16:26:25 +01:00
}
upsertCmd := & models . UpsertUserCommand {
2020-02-06 07:49:58 +01:00
ReqContext : c ,
2022-08-02 16:58:05 +02:00
ExternalUser : userInfo ,
2020-12-11 11:44:44 +01:00
SignupAllowed : hs . Cfg . LDAPAllowSignup ,
2022-07-15 09:21:09 +00:00
UserLookupParams : models . UserLookupParams {
2022-08-02 16:58:05 +02:00
UserID : & usr . ID , // Upsert by ID only
2022-07-15 09:21:09 +00:00
Email : nil ,
Login : nil ,
},
2019-09-13 16:26:25 +01:00
}
2022-02-09 18:45:31 +08:00
err = hs . Login . UpsertUser ( c . Req . Context (), upsertCmd )
2019-09-13 16:26:25 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusInternalServerError , "Failed to update the user" , err )
2019-09-13 16:26:25 +01:00
}
2021-01-15 14:43:20 +01:00
return response . Success ( "User synced successfully" )
2019-09-13 16:26:25 +01:00
}
2022-07-27 16:54:37 +03:00
// swagger:route GET /admin/ldap/{user_name} admin_ldap getUserFromLDAP
//
// Finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.user:read`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
2021-01-15 14:43:20 +01:00
func ( hs * HTTPServer ) GetUserFromLDAP ( c * models . ReqContext ) response . Response {
2019-09-04 15:29:14 +01:00
if ! ldap . IsEnabled () {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "LDAP is not enabled" , nil )
2019-09-04 15:29:14 +01:00
}
2020-12-11 11:44:44 +01:00
ldapConfig , err := getLDAPConfig ( hs . Cfg )
2019-09-03 18:34:44 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Failed to obtain the LDAP configuration" , err )
2019-09-03 18:34:44 +01:00
}
2022-04-22 15:45:54 +02:00
multiLDAP := newLDAP ( ldapConfig . Servers )
2019-09-03 18:34:44 +01:00
2021-10-11 14:30:59 +02:00
username := web . Params ( c . Req )[ ":username" ]
2019-09-03 18:34:44 +01:00
if len ( username ) == 0 {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Validation error. You must specify an username" , nil )
2019-09-03 18:34:44 +01:00
}
2022-04-22 15:45:54 +02:00
user , serverConfig , err := multiLDAP . User ( username )
if user == nil || err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusNotFound , "No user was found in the LDAP server(s) with that username" , err )
2019-09-03 18:34:44 +01:00
}
2020-12-15 09:32:06 +01:00
ldapLogger . Debug ( "user found" , "user" , user )
2019-09-08 11:48:47 +01:00
2019-09-03 18:34:44 +01:00
name , surname := splitName ( user . Name )
u := & LDAPUserDTO {
Name : & LDAPAttribute { serverConfig . Attr . Name , name },
Surname : & LDAPAttribute { serverConfig . Attr . Surname , surname },
Email : & LDAPAttribute { serverConfig . Attr . Email , user . Email },
Username : & LDAPAttribute { serverConfig . Attr . Username , user . Login },
IsGrafanaAdmin : user . IsGrafanaAdmin ,
IsDisabled : user . IsDisabled ,
}
2022-04-22 15:45:54 +02:00
unmappedUserGroups := map [ string ] struct {}{}
2019-09-19 16:13:38 +01:00
for _ , userGroup := range user . Groups {
2022-04-22 15:45:54 +02:00
unmappedUserGroups [ strings . ToLower ( userGroup )] = struct {}{}
}
2019-09-03 18:34:44 +01:00
2022-08-10 08:20:23 +00:00
orgIDs := [] int64 {} // IDs of the orgs the user is a member of
2022-08-10 11:56:48 +02:00
orgRolesMap := map [ int64 ] org . RoleType {}
2022-04-22 15:45:54 +02:00
for _ , group := range serverConfig . Groups {
// only use the first match for each org
if orgRolesMap [ group . OrgId ] != "" {
continue
2019-09-19 16:13:38 +01:00
}
2022-04-22 15:45:54 +02:00
if ldap . IsMemberOf ( user . Groups , group . GroupDN ) {
orgRolesMap [ group . OrgId ] = group . OrgRole
u . OrgRoles = append ( u . OrgRoles , LDAPRoleDTO { GroupDN : group . GroupDN ,
OrgId : group . OrgId , OrgRole : group . OrgRole })
delete ( unmappedUserGroups , strings . ToLower ( group . GroupDN ))
2022-08-10 08:20:23 +00:00
orgIDs = append ( orgIDs , group . OrgId )
2019-09-03 18:34:44 +01:00
}
}
2022-04-22 15:45:54 +02:00
for userGroup := range unmappedUserGroups {
u . OrgRoles = append ( u . OrgRoles , LDAPRoleDTO { GroupDN : userGroup })
}
2019-09-03 18:34:44 +01:00
2020-12-15 09:32:06 +01:00
ldapLogger . Debug ( "mapping org roles" , "orgsRoles" , u . OrgRoles )
2022-09-20 09:55:40 +02:00
if err := u . FetchOrgs ( c . Req . Context (), hs . orgService ); err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "An organization was not found - Please verify your LDAP configuration" , err )
2019-09-03 18:34:44 +01:00
}
2022-08-10 08:20:23 +00:00
u . Teams , err = hs . ldapGroups . GetTeams ( user . Groups , orgIDs )
2022-02-01 12:03:21 +01:00
if err != nil {
2021-01-15 14:43:20 +01:00
return response . Error ( http . StatusBadRequest , "Unable to find the teams for this user" , err )
2019-09-08 11:48:47 +01:00
}
2022-04-15 14:01:58 +02:00
return response . JSON ( http . StatusOK , u )
2019-09-03 18:34:44 +01:00
}
// splitName receives the full name of a user and splits it into two parts: A name and a surname.
func splitName ( name string ) ( string , string ) {
names := util . SplitString ( name )
switch len ( names ) {
case 0 :
return "" , ""
case 1 :
return names [ 0 ], ""
default :
return names [ 0 ], names [ 1 ]
}
}
2022-07-27 16:54:37 +03:00
// swagger:parameters getUserFromLDAP
type GetLDAPUserParams struct {
// in:path
// required:true
UserName string `json:"user_name"`
}
// swagger:parameters postSyncUserWithLDAP
type SyncLDAPUserParams struct {
// in:path
// required:true
UserID int64 `json:"user_id"`
}