ldap_debug.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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 LDAPRoleDTO 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 []LDAPRoleDTO `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. if orgDTO.OrgId < 1 {
  77. continue
  78. }
  79. orgName := orgNamesById[orgDTO.OrgId]
  80. if orgName != "" {
  81. user.OrgRoles[i].OrgName = orgName
  82. } else {
  83. return errOrganizationNotFound(orgDTO.OrgId)
  84. }
  85. }
  86. return nil
  87. }
  88. // ReloadLDAPCfg reloads the LDAP configuration
  89. func (server *HTTPServer) ReloadLDAPCfg() Response {
  90. if !ldap.IsEnabled() {
  91. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  92. }
  93. err := ldap.ReloadConfig()
  94. if err != nil {
  95. return Error(http.StatusInternalServerError, "Failed to reload LDAP config", err)
  96. }
  97. return Success("LDAP config reloaded")
  98. }
  99. // GetLDAPStatus attempts to connect to all the configured LDAP servers and returns information on whenever they're availabe or not.
  100. func (server *HTTPServer) GetLDAPStatus(c *models.ReqContext) Response {
  101. if !ldap.IsEnabled() {
  102. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  103. }
  104. ldapConfig, err := getLDAPConfig()
  105. if err != nil {
  106. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
  107. }
  108. ldap := newLDAP(ldapConfig.Servers)
  109. if ldap == nil {
  110. return Error(http.StatusInternalServerError, "Failed to find the LDAP server", nil)
  111. }
  112. statuses, err := ldap.Ping()
  113. if err != nil {
  114. return Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
  115. }
  116. serverDTOs := []*LDAPServerDTO{}
  117. for _, status := range statuses {
  118. s := &LDAPServerDTO{
  119. Host: status.Host,
  120. Available: status.Available,
  121. Port: status.Port,
  122. }
  123. if status.Error != nil {
  124. s.Error = status.Error.Error()
  125. }
  126. serverDTOs = append(serverDTOs, s)
  127. }
  128. return JSON(http.StatusOK, serverDTOs)
  129. }
  130. // PostSyncUserWithLDAP enables a single Grafana user to be synchronized against LDAP
  131. func (server *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) Response {
  132. if !ldap.IsEnabled() {
  133. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  134. }
  135. ldapConfig, err := getLDAPConfig()
  136. if err != nil {
  137. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
  138. }
  139. userId := c.ParamsInt64(":id")
  140. query := models.GetUserByIdQuery{Id: userId}
  141. if err := bus.Dispatch(&query); err != nil { // validate the userId exists
  142. if err == models.ErrUserNotFound {
  143. return Error(404, models.ErrUserNotFound.Error(), nil)
  144. }
  145. return Error(500, "Failed to get user", err)
  146. }
  147. authModuleQuery := &models.GetAuthInfoQuery{UserId: query.Result.Id, AuthModule: models.AuthModuleLDAP}
  148. if err := bus.Dispatch(authModuleQuery); err != nil { // validate the userId comes from LDAP
  149. if err == models.ErrUserNotFound {
  150. return Error(404, models.ErrUserNotFound.Error(), nil)
  151. }
  152. return Error(500, "Failed to get user", err)
  153. }
  154. ldapServer := newLDAP(ldapConfig.Servers)
  155. user, _, err := ldapServer.User(query.Result.Login)
  156. if err != nil {
  157. if err == ldap.ErrCouldNotFindUser { // User was not in the LDAP server - we need to take action:
  158. if setting.AdminUser == query.Result.Login { // User is *the* Grafana Admin. We cannot disable it.
  159. errMsg := fmt.Sprintf(`Refusing to sync grafana super admin "%s" - it would be disabled`, query.Result.Login)
  160. logger.Error(errMsg)
  161. return Error(http.StatusBadRequest, errMsg, err)
  162. }
  163. // Since the user was not in the LDAP server. Let's disable it.
  164. err := login.DisableExternalUser(query.Result.Login)
  165. if err != nil {
  166. return Error(http.StatusInternalServerError, "Failed to disable the user", err)
  167. }
  168. err = tokenService.RevokeAllUserTokens(context.TODO(), userId)
  169. if err != nil {
  170. return Error(http.StatusInternalServerError, "Failed to remove session tokens for the user", err)
  171. }
  172. return Success("User disabled without any updates in the information") // should this be a success?
  173. }
  174. }
  175. upsertCmd := &models.UpsertUserCommand{
  176. ExternalUser: user,
  177. SignupAllowed: setting.LDAPAllowSignup,
  178. }
  179. err = bus.Dispatch(upsertCmd)
  180. if err != nil {
  181. return Error(http.StatusInternalServerError, "Failed to udpate the user", err)
  182. }
  183. return Success("User synced successfully")
  184. }
  185. // GetUserFromLDAP finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
  186. func (server *HTTPServer) GetUserFromLDAP(c *models.ReqContext) Response {
  187. if !ldap.IsEnabled() {
  188. return Error(http.StatusBadRequest, "LDAP is not enabled", nil)
  189. }
  190. ldapConfig, err := getLDAPConfig()
  191. if err != nil {
  192. return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
  193. }
  194. ldap := newLDAP(ldapConfig.Servers)
  195. username := c.Params(":username")
  196. if len(username) == 0 {
  197. return Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
  198. }
  199. user, serverConfig, err := ldap.User(username)
  200. if user == nil {
  201. return Error(http.StatusNotFound, "No user was found in the LDAP server(s) with that username", err)
  202. }
  203. logger.Debug("user found", "user", user)
  204. name, surname := splitName(user.Name)
  205. u := &LDAPUserDTO{
  206. Name: &LDAPAttribute{serverConfig.Attr.Name, name},
  207. Surname: &LDAPAttribute{serverConfig.Attr.Surname, surname},
  208. Email: &LDAPAttribute{serverConfig.Attr.Email, user.Email},
  209. Username: &LDAPAttribute{serverConfig.Attr.Username, user.Login},
  210. IsGrafanaAdmin: user.IsGrafanaAdmin,
  211. IsDisabled: user.IsDisabled,
  212. }
  213. orgRoles := []LDAPRoleDTO{}
  214. // First, let's find the groupDN that we did match by inspecting the assigned user OrgRoles.
  215. for _, group := range serverConfig.Groups {
  216. orgRole, ok := user.OrgRoles[group.OrgId]
  217. if ok && orgRole == group.OrgRole {
  218. r := &LDAPRoleDTO{GroupDN: group.GroupDN, OrgId: group.OrgId, OrgRole: group.OrgRole}
  219. orgRoles = append(orgRoles, *r)
  220. }
  221. }
  222. // Then, we find what we did not match by inspecting the list of groups returned from
  223. // LDAP against what we have already matched above.
  224. for _, userGroup := range user.Groups {
  225. var matches int
  226. for _, orgRole := range orgRoles {
  227. if orgRole.GroupDN == userGroup { // we already matched it
  228. matches++
  229. }
  230. }
  231. if matches < 1 {
  232. r := &LDAPRoleDTO{GroupDN: userGroup}
  233. orgRoles = append(orgRoles, *r)
  234. }
  235. }
  236. u.OrgRoles = orgRoles
  237. logger.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
  238. err = u.FetchOrgs()
  239. if err != nil {
  240. return Error(http.StatusBadRequest, "An oganization was not found - Please verify your LDAP configuration", err)
  241. }
  242. cmd := &models.GetTeamsForLDAPGroupCommand{Groups: user.Groups}
  243. err = bus.Dispatch(cmd)
  244. if err != bus.ErrHandlerNotFound && err != nil {
  245. return Error(http.StatusBadRequest, "Unable to find the teams for this user", err)
  246. }
  247. u.Teams = cmd.Result
  248. return JSON(200, u)
  249. }
  250. // splitName receives the full name of a user and splits it into two parts: A name and a surname.
  251. func splitName(name string) (string, string) {
  252. names := util.SplitString(name)
  253. switch len(names) {
  254. case 0:
  255. return "", ""
  256. case 1:
  257. return names[0], ""
  258. default:
  259. return names[0], names[1]
  260. }
  261. }