ldap_debug.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package api
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/infra/log"
  7. "github.com/grafana/grafana/pkg/models"
  8. "github.com/grafana/grafana/pkg/services/ldap"
  9. "github.com/grafana/grafana/pkg/services/multildap"
  10. "github.com/grafana/grafana/pkg/util"
  11. )
  12. var (
  13. getLDAPConfig = multildap.GetConfig
  14. newLDAP = multildap.New
  15. logger = log.New("LDAP.debug")
  16. errOrganizationNotFound = func(orgId int64) error {
  17. return fmt.Errorf("Unable to find organization with ID '%d'", orgId)
  18. }
  19. )
  20. // 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.
  21. type LDAPAttribute struct {
  22. ConfigAttributeValue string `json:"cfgAttrValue"`
  23. LDAPAttributeValue string `json:"ldapValue"`
  24. }
  25. // RoleDTO is a serializer for mapped roles from LDAP
  26. type RoleDTO struct {
  27. OrgId int64 `json:"orgId"`
  28. OrgName string `json:"orgName"`
  29. OrgRole models.RoleType `json:"orgRole"`
  30. GroupDN string `json:"groupDN"`
  31. }
  32. // LDAPUserDTO is a serializer for users mapped from LDAP
  33. type LDAPUserDTO struct {
  34. Name *LDAPAttribute `json:"name"`
  35. Surname *LDAPAttribute `json:"surname"`
  36. Email *LDAPAttribute `json:"email"`
  37. Username *LDAPAttribute `json:"login"`
  38. IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
  39. IsDisabled bool `json:"isDisabled"`
  40. OrgRoles []RoleDTO `json:"roles"`
  41. Teams []models.TeamOrgGroupDTO `json:"teams"`
  42. }
  43. // FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
  44. func (user *LDAPUserDTO) FetchOrgs() error {
  45. orgIds := []int64{}
  46. for _, or := range user.OrgRoles {
  47. orgIds = append(orgIds, or.OrgId)
  48. }
  49. q := &models.SearchOrgsQuery{}
  50. q.Ids = orgIds
  51. if err := bus.Dispatch(q); err != nil {
  52. return err
  53. }
  54. orgNamesById := map[int64]string{}
  55. for _, org := range q.Result {
  56. orgNamesById[org.Id] = org.Name
  57. }
  58. for i, orgDTO := range user.OrgRoles {
  59. orgName := orgNamesById[orgDTO.OrgId]
  60. if orgName != "" {
  61. user.OrgRoles[i].OrgName = orgName
  62. } else {
  63. return errOrganizationNotFound(orgDTO.OrgId)
  64. }
  65. }
  66. return nil
  67. }
  68. // LDAPServerDTO is a serializer for LDAP server statuses
  69. type LDAPServerDTO struct {
  70. Host string `json:"host"`
  71. Port int `json:"port"`
  72. Available bool `json:"available"`
  73. Error string `json:"error"`
  74. }
  75. // ReloadLDAPCfg reloads the LDAP configuration
  76. func (server *HTTPServer) ReloadLDAPCfg() Response {
  77. if !ldap.IsEnabled() {
  78. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  79. }
  80. err := ldap.ReloadConfig()
  81. if err != nil {
  82. return Error(http.StatusInternalServerError, "Failed to reload ldap config.", err)
  83. }
  84. return Success("LDAP config reloaded")
  85. }
  86. // GetLDAPStatus attempts to connect to all the configured LDAP servers and returns information on whenever they're availabe or not.
  87. func (server *HTTPServer) GetLDAPStatus(c *models.ReqContext) Response {
  88. if !ldap.IsEnabled() {
  89. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  90. }
  91. ldapConfig, err := getLDAPConfig()
  92. if err != nil {
  93. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again.", err)
  94. }
  95. ldap := newLDAP(ldapConfig.Servers)
  96. statuses, err := ldap.Ping()
  97. if err != nil {
  98. return Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
  99. }
  100. serverDTOs := []*LDAPServerDTO{}
  101. for _, status := range statuses {
  102. s := &LDAPServerDTO{
  103. Host: status.Host,
  104. Available: status.Available,
  105. Port: status.Port,
  106. }
  107. if status.Error != nil {
  108. s.Error = status.Error.Error()
  109. }
  110. serverDTOs = append(serverDTOs, s)
  111. }
  112. return JSON(http.StatusOK, serverDTOs)
  113. }
  114. // GetUserFromLDAP finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
  115. func (server *HTTPServer) GetUserFromLDAP(c *models.ReqContext) Response {
  116. if !ldap.IsEnabled() {
  117. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  118. }
  119. ldapConfig, err := getLDAPConfig()
  120. if err != nil {
  121. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
  122. }
  123. ldap := newLDAP(ldapConfig.Servers)
  124. username := c.Params(":username")
  125. if len(username) == 0 {
  126. return Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
  127. }
  128. user, serverConfig, err := ldap.User(username)
  129. if user == nil {
  130. return Error(http.StatusNotFound, "No user was found on the LDAP server(s)", err)
  131. }
  132. logger.Debug("user found", "user", user)
  133. name, surname := splitName(user.Name)
  134. u := &LDAPUserDTO{
  135. Name: &LDAPAttribute{serverConfig.Attr.Name, name},
  136. Surname: &LDAPAttribute{serverConfig.Attr.Surname, surname},
  137. Email: &LDAPAttribute{serverConfig.Attr.Email, user.Email},
  138. Username: &LDAPAttribute{serverConfig.Attr.Username, user.Login},
  139. IsGrafanaAdmin: user.IsGrafanaAdmin,
  140. IsDisabled: user.IsDisabled,
  141. }
  142. orgRoles := []RoleDTO{}
  143. for _, g := range serverConfig.Groups {
  144. role := &RoleDTO{}
  145. if isMatchToLDAPGroup(user, g) {
  146. role.OrgId = g.OrgID
  147. role.OrgRole = user.OrgRoles[g.OrgID]
  148. role.GroupDN = g.GroupDN
  149. orgRoles = append(orgRoles, *role)
  150. } else {
  151. role.OrgId = g.OrgID
  152. role.GroupDN = g.GroupDN
  153. orgRoles = append(orgRoles, *role)
  154. }
  155. }
  156. u.OrgRoles = orgRoles
  157. logger.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
  158. err = u.FetchOrgs()
  159. if err != nil {
  160. return Error(http.StatusBadRequest, "An oganization was not found - Please verify your LDAP configuration", err)
  161. }
  162. cmd := &models.GetTeamsForLDAPGroupCommand{Groups: user.Groups}
  163. err = bus.Dispatch(cmd)
  164. if err != bus.ErrHandlerNotFound && err != nil {
  165. return Error(http.StatusBadRequest, "Unable to find the teams for this user", err)
  166. }
  167. u.Teams = cmd.Result
  168. return JSON(200, u)
  169. }
  170. // isMatchToLDAPGroup determines if we were able to match an LDAP group to an organization+role.
  171. // Since we allow one role per organization. If it's set, we were able to match it.
  172. func isMatchToLDAPGroup(user *models.ExternalUserInfo, groupConfig *ldap.GroupToOrgRole) bool {
  173. return user.OrgRoles[groupConfig.OrgID] == groupConfig.OrgRole
  174. }
  175. // splitName receives the full name of a user and splits it into two parts: A name and a surname.
  176. func splitName(name string) (string, string) {
  177. names := util.SplitString(name)
  178. switch len(names) {
  179. case 0:
  180. return "", ""
  181. case 1:
  182. return names[0], ""
  183. default:
  184. return names[0], names[1]
  185. }
  186. }