| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package api
- import (
- "fmt"
- "net/http"
- "github.com/grafana/grafana/pkg/bus"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/ldap"
- "github.com/grafana/grafana/pkg/services/multildap"
- "github.com/grafana/grafana/pkg/util"
- )
- var (
- getLDAPConfig = multildap.GetConfig
- newLDAP = multildap.New
- logger = log.New("LDAP.debug")
- errOrganizationNotFound = func(orgId int64) error {
- return fmt.Errorf("Unable to find organization with ID '%d'", orgId)
- }
- )
- // 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
- type RoleDTO struct {
- OrgId int64 `json:"orgId"`
- OrgName string `json:"orgName"`
- OrgRole models.RoleType `json:"orgRole"`
- GroupDN string `json:"groupDN"`
- }
- // LDAPUserDTO is a serializer for users mapped from LDAP
- type LDAPUserDTO struct {
- Name *LDAPAttribute `json:"name"`
- Surname *LDAPAttribute `json:"surname"`
- Email *LDAPAttribute `json:"email"`
- Username *LDAPAttribute `json:"login"`
- IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
- IsDisabled bool `json:"isDisabled"`
- OrgRoles []RoleDTO `json:"roles"`
- Teams []models.TeamOrgGroupDTO `json:"teams"`
- }
- // FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
- func (user *LDAPUserDTO) FetchOrgs() error {
- orgIds := []int64{}
- for _, or := range user.OrgRoles {
- orgIds = append(orgIds, or.OrgId)
- }
- q := &models.SearchOrgsQuery{}
- q.Ids = orgIds
- if err := bus.Dispatch(q); err != nil {
- return err
- }
- orgNamesById := map[int64]string{}
- for _, org := range q.Result {
- orgNamesById[org.Id] = org.Name
- }
- for i, orgDTO := range user.OrgRoles {
- orgName := orgNamesById[orgDTO.OrgId]
- if orgName != "" {
- user.OrgRoles[i].OrgName = orgName
- } else {
- return errOrganizationNotFound(orgDTO.OrgId)
- }
- }
- return nil
- }
- // 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"`
- }
- // ReloadLDAPCfg reloads the LDAP configuration
- func (server *HTTPServer) ReloadLDAPCfg() Response {
- if !ldap.IsEnabled() {
- return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
- }
- err := ldap.ReloadConfig()
- if err != nil {
- return Error(http.StatusInternalServerError, "Failed to reload ldap config.", err)
- }
- return Success("LDAP config reloaded")
- }
- // GetLDAPStatus attempts to connect to all the configured LDAP servers and returns information on whenever they're availabe or not.
- func (server *HTTPServer) GetLDAPStatus(c *models.ReqContext) Response {
- if !ldap.IsEnabled() {
- return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
- }
- ldapConfig, err := getLDAPConfig()
- if err != nil {
- return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again.", err)
- }
- ldap := newLDAP(ldapConfig.Servers)
- statuses, err := ldap.Ping()
- if err != nil {
- return Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
- }
- 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)
- }
- return JSON(http.StatusOK, serverDTOs)
- }
- // GetUserFromLDAP finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
- func (server *HTTPServer) GetUserFromLDAP(c *models.ReqContext) Response {
- if !ldap.IsEnabled() {
- return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
- }
- ldapConfig, err := getLDAPConfig()
- if err != nil {
- return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
- }
- ldap := newLDAP(ldapConfig.Servers)
- username := c.Params(":username")
- if len(username) == 0 {
- return Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
- }
- user, serverConfig, err := ldap.User(username)
- if user == nil {
- return Error(http.StatusNotFound, "No user was found on the LDAP server(s)", err)
- }
- logger.Debug("user found", "user", user)
- 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,
- }
- orgRoles := []RoleDTO{}
- for _, g := range serverConfig.Groups {
- role := &RoleDTO{}
- if isMatchToLDAPGroup(user, g) {
- role.OrgId = g.OrgID
- role.OrgRole = user.OrgRoles[g.OrgID]
- role.GroupDN = g.GroupDN
- orgRoles = append(orgRoles, *role)
- } else {
- role.OrgId = g.OrgID
- role.GroupDN = g.GroupDN
- orgRoles = append(orgRoles, *role)
- }
- }
- u.OrgRoles = orgRoles
- logger.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
- err = u.FetchOrgs()
- if err != nil {
- return Error(http.StatusBadRequest, "An oganization was not found - Please verify your LDAP configuration", err)
- }
- cmd := &models.GetTeamsForLDAPGroupCommand{Groups: user.Groups}
- err = bus.Dispatch(cmd)
- if err != bus.ErrHandlerNotFound && err != nil {
- return Error(http.StatusBadRequest, "Unable to find the teams for this user", err)
- }
- u.Teams = cmd.Result
- return JSON(200, u)
- }
- // isMatchToLDAPGroup determines if we were able to match an LDAP group to an organization+role.
- // Since we allow one role per organization. If it's set, we were able to match it.
- func isMatchToLDAPGroup(user *models.ExternalUserInfo, groupConfig *ldap.GroupToOrgRole) bool {
- return user.OrgRoles[groupConfig.OrgID] == groupConfig.OrgRole
- }
- // 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]
- }
- }
|