ldap.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package ldap
  15. import (
  16. "context"
  17. "database/sql"
  18. "strconv"
  19. "strings"
  20. "github.com/go-ldap/ldap/v3"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/utils"
  25. api "yunion.io/x/onecloud/pkg/apis/identity"
  26. "yunion.io/x/onecloud/pkg/httperrors"
  27. "yunion.io/x/onecloud/pkg/keystone/driver"
  28. "yunion.io/x/onecloud/pkg/keystone/models"
  29. "yunion.io/x/onecloud/pkg/mcclient"
  30. "yunion.io/x/onecloud/pkg/util/ldaputils"
  31. )
  32. type SLDAPDriver struct {
  33. driver.SBaseIdentityDriver
  34. ldapConfig *api.SLDAPIdpConfigOptions
  35. }
  36. func NewLDAPDriver(idpId, idpName, template, targetDomainId string, conf api.TConfigs) (driver.IIdentityBackend, error) {
  37. base, err := driver.NewBaseIdentityDriver(idpId, idpName, template, targetDomainId, conf)
  38. if err != nil {
  39. return nil, errors.Wrap(err, "NewBaseIdentityDriver")
  40. }
  41. drv := SLDAPDriver{SBaseIdentityDriver: base}
  42. drv.SetVirtualObject(&drv)
  43. err = drv.prepareConfig()
  44. if err != nil {
  45. return nil, errors.Wrap(err, "prepareConfig")
  46. }
  47. return &drv, nil
  48. }
  49. func (drv *SLDAPDriver) prepareConfig() error {
  50. if drv.ldapConfig == nil {
  51. conf := api.SLDAPIdpConfigOptions{}
  52. switch drv.Template {
  53. case api.IdpTemplateMSSingleDomain:
  54. conf = MicrosoftActiveDirectorySingleDomainTemplate
  55. case api.IdpTemplateMSMultiDomain:
  56. conf = MicrosoftActiveDirectoryMultipleDomainTemplate
  57. case api.IdpTemplateOpenLDAPSingleDomain:
  58. conf = OpenLdapSingleDomainTemplate
  59. }
  60. confJson := jsonutils.Marshal(drv.Config["ldap"])
  61. err := confJson.Unmarshal(&conf)
  62. if err != nil {
  63. return errors.Wrap(err, "json.Unmarshal")
  64. }
  65. log.Debugf("%s %s %#v", drv.Config, confJson, drv.ldapConfig)
  66. drv.ldapConfig = &conf
  67. }
  68. return nil
  69. }
  70. func (ldap *SLDAPDriver) GetSsoRedirectUri(ctx context.Context, callbackUrl, state string) (string, error) {
  71. return "", errors.Wrap(httperrors.ErrNotSupported, "not a SSO driver")
  72. }
  73. func queryScope(scope string) int {
  74. if scope == api.QueryScopeOne {
  75. return ldap.ScopeSingleLevel
  76. } else {
  77. return ldap.ScopeWholeSubtree
  78. }
  79. }
  80. func (drv *SLDAPDriver) userQueryScope() int {
  81. scope := drv.ldapConfig.UserQueryScope
  82. if len(scope) == 0 {
  83. scope = drv.ldapConfig.QueryScope
  84. }
  85. return queryScope(scope)
  86. }
  87. func (drv *SLDAPDriver) groupQueryScope() int {
  88. scope := drv.ldapConfig.GroupQueryScope
  89. if len(scope) == 0 {
  90. scope = drv.ldapConfig.QueryScope
  91. }
  92. return queryScope(scope)
  93. }
  94. func (drv *SLDAPDriver) domainQueryScope() int {
  95. scope := drv.ldapConfig.DomainQueryScope
  96. if len(scope) == 0 {
  97. scope = drv.ldapConfig.QueryScope
  98. }
  99. return queryScope(scope)
  100. }
  101. func (drv *SLDAPDriver) userAttributeList() []string {
  102. attrs := []string{
  103. "dn",
  104. drv.ldapConfig.UserIdAttribute,
  105. drv.ldapConfig.UserNameAttribute,
  106. drv.ldapConfig.UserEnabledAttribute,
  107. }
  108. for _, m := range drv.ldapConfig.UserAdditionalAttribute {
  109. parts := strings.Split(m, ":")
  110. if len(parts) == 2 && !utils.IsInArray(parts[0], attrs) {
  111. attrs = append(attrs, parts[0])
  112. }
  113. }
  114. return attrs
  115. }
  116. func (drv *SLDAPDriver) groupAttributeList() []string {
  117. return []string{
  118. "dn",
  119. drv.ldapConfig.GroupIdAttribute,
  120. drv.ldapConfig.GroupNameAttribute,
  121. drv.ldapConfig.GroupMemberAttribute,
  122. }
  123. }
  124. func (drv *SLDAPDriver) domainAttributeList() []string {
  125. return []string{
  126. "dn",
  127. drv.ldapConfig.DomainIdAttribute,
  128. drv.ldapConfig.DomainNameAttribute,
  129. }
  130. }
  131. func (drv *SLDAPDriver) entry2Domain(entry *ldap.Entry) SDomainInfo {
  132. info := SDomainInfo{}
  133. info.DN = entry.DN
  134. info.Id = ldaputils.GetAttributeValue(entry, drv.ldapConfig.DomainIdAttribute)
  135. info.Name = ldaputils.GetAttributeValue(entry, drv.ldapConfig.DomainNameAttribute)
  136. return info
  137. }
  138. func (drv *SLDAPDriver) entry2Group(entry *ldap.Entry) SGroupInfo {
  139. info := SGroupInfo{}
  140. info.DN = entry.DN
  141. info.Id = ldaputils.GetAttributeValue(entry, drv.ldapConfig.GroupIdAttribute)
  142. info.Name = ldaputils.GetAttributeValue(entry, drv.ldapConfig.GroupNameAttribute)
  143. info.Members = ldaputils.GetAttributeValues(entry, drv.ldapConfig.GroupMemberAttribute)
  144. return info
  145. }
  146. func (drv *SLDAPDriver) entry2User(entry *ldap.Entry) SUserInfo {
  147. info := SUserInfo{}
  148. info.DN = entry.DN
  149. info.Id = ldaputils.GetAttributeValue(entry, drv.ldapConfig.UserIdAttribute)
  150. info.Name = ldaputils.GetAttributeValue(entry, drv.ldapConfig.UserNameAttribute)
  151. enabledStr := ldaputils.GetAttributeValue(entry, drv.ldapConfig.UserEnabledAttribute)
  152. if len(enabledStr) == 0 {
  153. enabledStr = drv.ldapConfig.UserEnabledDefault
  154. }
  155. if drv.ldapConfig.UserEnabledMask > 0 {
  156. enabled, _ := strconv.ParseInt(enabledStr, 0, 64)
  157. if (enabled & drv.ldapConfig.UserEnabledMask) != 0 {
  158. info.Enabled = true
  159. }
  160. } else {
  161. info.Enabled = utils.ToBool(enabledStr)
  162. }
  163. if drv.ldapConfig.UserEnabledInvert {
  164. info.Enabled = !info.Enabled
  165. }
  166. info.Extra = make(map[string]string)
  167. for _, m := range drv.ldapConfig.UserAdditionalAttribute {
  168. parts := strings.Split(m, ":")
  169. if len(parts) == 2 {
  170. info.Extra[parts[1]] = ldaputils.GetAttributeValue(entry, parts[0])
  171. }
  172. }
  173. return info
  174. }
  175. func (drv *SLDAPDriver) getClient() (*ldaputils.SLDAPClient, error) {
  176. cli := ldaputils.NewLDAPClient(
  177. drv.ldapConfig.Url,
  178. drv.ldapConfig.User,
  179. drv.ldapConfig.Password,
  180. drv.ldapConfig.Suffix,
  181. false,
  182. )
  183. err := cli.Connect()
  184. if err != nil {
  185. return nil, errors.Wrap(err, "Connect")
  186. }
  187. return cli, nil
  188. }
  189. func (drv *SLDAPDriver) getDomainTreeDN() string {
  190. if len(drv.ldapConfig.DomainTreeDN) > 0 {
  191. return drv.ldapConfig.DomainTreeDN
  192. }
  193. return drv.ldapConfig.Suffix
  194. }
  195. func (drv *SLDAPDriver) getUserTreeDN() string {
  196. if len(drv.ldapConfig.UserTreeDN) > 0 {
  197. return drv.ldapConfig.UserTreeDN
  198. }
  199. return drv.ldapConfig.Suffix
  200. }
  201. func (drv *SLDAPDriver) getGroupTreeDN() string {
  202. if len(drv.ldapConfig.GroupTreeDN) > 0 {
  203. return drv.ldapConfig.GroupTreeDN
  204. }
  205. return drv.ldapConfig.Suffix
  206. }
  207. func (drv *SLDAPDriver) Authenticate(ctx context.Context, ident mcclient.SAuthenticationIdentity) (*api.SUserExtended, error) {
  208. cli, err := drv.getClient()
  209. if err != nil {
  210. return nil, errors.Wrap(err, "getClient")
  211. }
  212. defer cli.Close()
  213. usrExt, err := models.UserManager.FetchUserExtended(
  214. ident.Password.User.Id,
  215. ident.Password.User.Name,
  216. ident.Password.User.Domain.Id,
  217. ident.Password.User.Domain.Name,
  218. )
  219. if err != nil {
  220. return nil, errors.Wrap(err, "UserManager.FetchUserExtended")
  221. }
  222. var userTreeDN string
  223. if len(drv.ldapConfig.DomainTreeDN) > 0 {
  224. // import domains
  225. idMap, err := models.IdmappingManager.FetchFirstEntity(usrExt.DomainId, api.IdMappingEntityDomain)
  226. if err != nil {
  227. return nil, errors.Wrap(err, "IdmappingManager.FetchEntity for domain")
  228. }
  229. var searchEntry *ldap.Entry
  230. err = drv.searchDomainEntries(cli, idMap.IdpEntityId,
  231. func(entry *ldap.Entry) error {
  232. searchEntry = entry
  233. return ldaputils.StopSearch
  234. })
  235. if err != nil {
  236. return nil, errors.Wrap(err, "drv.searchDomainEntries")
  237. }
  238. if searchEntry == nil {
  239. return nil, errors.Error("fail to find domain DN")
  240. }
  241. userTreeDN = searchEntry.DN
  242. } else {
  243. userTreeDN = drv.getUserTreeDN()
  244. }
  245. usrIdmaps, err := models.IdmappingManager.FetchEntities(usrExt.Id, api.IdMappingEntityUser)
  246. if err != nil && errors.Cause(err) != sql.ErrNoRows {
  247. return nil, errors.Wrap(err, "IdmappingManager.FetchEntity for user")
  248. }
  249. var usrIdmap *models.SIdmapping
  250. for i := range usrIdmaps {
  251. if usrIdmaps[i].IdpId == drv.IdpId {
  252. usrIdmap = &usrIdmaps[i]
  253. break
  254. }
  255. }
  256. if usrIdmap == nil {
  257. return nil, errors.Wrap(httperrors.ErrInvalidCredential, "user not found in identity provider")
  258. }
  259. username := usrIdmap.IdpEntityId
  260. password := ident.Password.User.Password
  261. _, err = cli.Authenticate(
  262. userTreeDN,
  263. drv.ldapConfig.UserObjectclass,
  264. drv.ldapConfig.UserIdAttribute,
  265. username,
  266. password,
  267. drv.ldapConfig.UserFilter,
  268. nil,
  269. drv.userQueryScope(),
  270. )
  271. if err != nil {
  272. log.Errorf("LDAP AUTH error: %s", err)
  273. if errors.Cause(err) == ldaputils.ErrUserNotFound {
  274. return nil, httperrors.ErrUserNotFound
  275. }
  276. if errors.Cause(err) == ldaputils.ErrUserBadCredential {
  277. return nil, httperrors.ErrWrongPassword
  278. }
  279. return nil, errors.Wrap(err, "Authenticate error")
  280. }
  281. usrExt.AuditIds = []string{username}
  282. return usrExt, nil
  283. }