ldap_debug.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package api
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/infra/log"
  8. "github.com/grafana/grafana/pkg/login"
  9. "github.com/grafana/grafana/pkg/models"
  10. "github.com/grafana/grafana/pkg/services/ldap"
  11. "github.com/grafana/grafana/pkg/services/multildap"
  12. "github.com/grafana/grafana/pkg/setting"
  13. "github.com/grafana/grafana/pkg/util"
  14. )
  15. var (
  16. getLDAPConfig = multildap.GetConfig
  17. newLDAP = multildap.New
  18. tokenService = AuthToken{}.TokenService
  19. logger = log.New("LDAP.debug")
  20. errOrganizationNotFound = func(orgId int64) error {
  21. return fmt.Errorf("Unable to find organization with ID '%d'", orgId)
  22. }
  23. )
  24. // 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.
  25. type LDAPAttribute struct {
  26. ConfigAttributeValue string `json:"cfgAttrValue"`
  27. LDAPAttributeValue string `json:"ldapValue"`
  28. }
  29. // RoleDTO is a serializer for mapped roles from LDAP
  30. type RoleDTO struct {
  31. OrgId int64 `json:"orgId"`
  32. OrgName string `json:"orgName"`
  33. OrgRole models.RoleType `json:"orgRole"`
  34. GroupDN string `json:"groupDN"`
  35. }
  36. // LDAPUserDTO is a serializer for users mapped from LDAP
  37. type LDAPUserDTO struct {
  38. Name *LDAPAttribute `json:"name"`
  39. Surname *LDAPAttribute `json:"surname"`
  40. Email *LDAPAttribute `json:"email"`
  41. Username *LDAPAttribute `json:"login"`
  42. IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
  43. IsDisabled bool `json:"isDisabled"`
  44. OrgRoles []RoleDTO `json:"roles"`
  45. Teams []models.TeamOrgGroupDTO `json:"teams"`
  46. }
  47. // LDAPServerDTO is a serializer for LDAP server statuses
  48. type LDAPServerDTO struct {
  49. Host string `json:"host"`
  50. Port int `json:"port"`
  51. Available bool `json:"available"`
  52. Error string `json:"error"`
  53. }
  54. type AuthToken struct {
  55. TokenService TokenRevoker `inject:""`
  56. }
  57. type TokenRevoker interface {
  58. RevokeAllUserTokens(context.Context, int64) error
  59. }
  60. // FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
  61. func (user *LDAPUserDTO) FetchOrgs() error {
  62. orgIds := []int64{}
  63. for _, or := range user.OrgRoles {
  64. orgIds = append(orgIds, or.OrgId)
  65. }
  66. q := &models.SearchOrgsQuery{}
  67. q.Ids = orgIds
  68. if err := bus.Dispatch(q); err != nil {
  69. return err
  70. }
  71. orgNamesById := map[int64]string{}
  72. for _, org := range q.Result {
  73. orgNamesById[org.Id] = org.Name
  74. }
  75. for i, orgDTO := range user.OrgRoles {
  76. orgName := orgNamesById[orgDTO.OrgId]
  77. if orgName != "" {
  78. user.OrgRoles[i].OrgName = orgName
  79. } else {
  80. return errOrganizationNotFound(orgDTO.OrgId)
  81. }
  82. }
  83. return nil
  84. }
  85. // ReloadLDAPCfg reloads the LDAP configuration
  86. func (server *HTTPServer) ReloadLDAPCfg() Response {
  87. if !ldap.IsEnabled() {
  88. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  89. }
  90. err := ldap.ReloadConfig()
  91. if err != nil {
  92. return Error(http.StatusInternalServerError, "Failed to reload LDAP config", err)
  93. }
  94. return Success("LDAP config reloaded")
  95. }
  96. // GetLDAPStatus attempts to connect to all the configured LDAP servers and returns information on whenever they're availabe or not.
  97. func (server *HTTPServer) GetLDAPStatus(c *models.ReqContext) Response {
  98. if !ldap.IsEnabled() {
  99. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  100. }
  101. ldapConfig, err := getLDAPConfig()
  102. if err != nil {
  103. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
  104. }
  105. ldap := newLDAP(ldapConfig.Servers)
  106. statuses, err := ldap.Ping()
  107. if err != nil {
  108. return Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
  109. }
  110. serverDTOs := []*LDAPServerDTO{}
  111. for _, status := range statuses {
  112. s := &LDAPServerDTO{
  113. Host: status.Host,
  114. Available: status.Available,
  115. Port: status.Port,
  116. }
  117. if status.Error != nil {
  118. s.Error = status.Error.Error()
  119. }
  120. serverDTOs = append(serverDTOs, s)
  121. }
  122. return JSON(http.StatusOK, serverDTOs)
  123. }
  124. // PostSyncUserWithLDAP enables a single Grafana user to be synchronized against LDAP
  125. func (server *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) Response {
  126. if !ldap.IsEnabled() {
  127. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  128. }
  129. ldapConfig, err := getLDAPConfig()
  130. if err != nil {
  131. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
  132. }
  133. userId := c.ParamsInt64(":id")
  134. query := models.GetUserByIdQuery{Id: userId}
  135. if err := bus.Dispatch(&query); err != nil { // validate the userId exists
  136. if err == models.ErrUserNotFound {
  137. return Error(404, models.ErrUserNotFound.Error(), nil)
  138. }
  139. return Error(500, "Failed to get user", err)
  140. }
  141. authModuleQuery := &models.GetAuthInfoQuery{UserId: query.Result.Id, AuthModule: models.AuthModuleLDAP}
  142. if err := bus.Dispatch(authModuleQuery); err != nil { // validate the userId comes from LDAP
  143. if err == models.ErrUserNotFound {
  144. return Error(404, models.ErrUserNotFound.Error(), nil)
  145. }
  146. return Error(500, "Failed to get user", err)
  147. }
  148. ldapServer := newLDAP(ldapConfig.Servers)
  149. user, _, err := ldapServer.User(query.Result.Login)
  150. if err != nil {
  151. if err == ldap.ErrCouldNotFindUser { // User was not in the LDAP server - we need to take action:
  152. if setting.AdminUser == query.Result.Login { // User is *the* Grafana Admin. We cannot disable it.
  153. errMsg := fmt.Sprintf(`Refusing to sync grafana super admin "%s" - it would be disabled`, query.Result.Login)
  154. logger.Error(errMsg)
  155. return Error(http.StatusBadRequest, errMsg, err)
  156. }
  157. // Since the user was not in the LDAP server. Let's disable it.
  158. err := login.DisableExternalUser(query.Result.Login)
  159. if err != nil {
  160. return Error(http.StatusInternalServerError, "Failed to disable the user", err)
  161. }
  162. err = tokenService.RevokeAllUserTokens(context.TODO(), userId)
  163. if err != nil {
  164. return Error(http.StatusInternalServerError, "Failed to remove session tokens for the user", err)
  165. }
  166. return Success("User disabled without any updates in the information") // should this be a success?
  167. }
  168. }
  169. upsertCmd := &models.UpsertUserCommand{
  170. ExternalUser: user,
  171. SignupAllowed: setting.LDAPAllowSignup,
  172. }
  173. err = bus.Dispatch(upsertCmd)
  174. if err != nil {
  175. return Error(http.StatusInternalServerError, "Failed to udpate the user", err)
  176. }
  177. return Success("User synced successfully")
  178. }
  179. // GetUserFromLDAP finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
  180. func (server *HTTPServer) GetUserFromLDAP(c *models.ReqContext) Response {
  181. if !ldap.IsEnabled() {
  182. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  183. }
  184. ldapConfig, err := getLDAPConfig()
  185. if err != nil {
  186. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
  187. }
  188. ldap := newLDAP(ldapConfig.Servers)
  189. username := c.Params(":username")
  190. if len(username) == 0 {
  191. return Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
  192. }
  193. user, serverConfig, err := ldap.User(username)
  194. if user == nil {
  195. return Error(http.StatusNotFound, "No user was found on the LDAP server(s)", err)
  196. }
  197. logger.Debug("user found", "user", user)
  198. name, surname := splitName(user.Name)
  199. u := &LDAPUserDTO{
  200. Name: &LDAPAttribute{serverConfig.Attr.Name, name},
  201. Surname: &LDAPAttribute{serverConfig.Attr.Surname, surname},
  202. Email: &LDAPAttribute{serverConfig.Attr.Email, user.Email},
  203. Username: &LDAPAttribute{serverConfig.Attr.Username, user.Login},
  204. IsGrafanaAdmin: user.IsGrafanaAdmin,
  205. IsDisabled: user.IsDisabled,
  206. }
  207. orgRoles := []RoleDTO{}
  208. for _, g := range serverConfig.Groups {
  209. role := &RoleDTO{}
  210. if isMatchToLDAPGroup(user, g) {
  211. role.OrgId = g.OrgID
  212. role.OrgRole = user.OrgRoles[g.OrgID]
  213. role.GroupDN = g.GroupDN
  214. orgRoles = append(orgRoles, *role)
  215. } else {
  216. role.OrgId = g.OrgID
  217. role.GroupDN = g.GroupDN
  218. orgRoles = append(orgRoles, *role)
  219. }
  220. }
  221. u.OrgRoles = orgRoles
  222. logger.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
  223. err = u.FetchOrgs()
  224. if err != nil {
  225. return Error(http.StatusBadRequest, "An oganization was not found - Please verify your LDAP configuration", err)
  226. }
  227. cmd := &models.GetTeamsForLDAPGroupCommand{Groups: user.Groups}
  228. err = bus.Dispatch(cmd)
  229. if err != bus.ErrHandlerNotFound && err != nil {
  230. return Error(http.StatusBadRequest, "Unable to find the teams for this user", err)
  231. }
  232. u.Teams = cmd.Result
  233. return JSON(200, u)
  234. }
  235. // isMatchToLDAPGroup determines if we were able to match an LDAP group to an organization+role.
  236. // Since we allow one role per organization. If it's set, we were able to match it.
  237. func isMatchToLDAPGroup(user *models.ExternalUserInfo, groupConfig *ldap.GroupToOrgRole) bool {
  238. return user.OrgRoles[groupConfig.OrgID] == groupConfig.OrgRole
  239. }
  240. // splitName receives the full name of a user and splits it into two parts: A name and a surname.
  241. func splitName(name string) (string, string) {
  242. names := util.SplitString(name)
  243. switch len(names) {
  244. case 0:
  245. return "", ""
  246. case 1:
  247. return names[0], ""
  248. default:
  249. return names[0], names[1]
  250. }
  251. }