ldap.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. package ldap
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "regexp"
  7. "strconv"
  8. "github.com/go-ldap/ldap"
  9. auth "imuslab.com/arozos/mod/auth"
  10. "imuslab.com/arozos/mod/auth/ldap/ldapreader"
  11. "imuslab.com/arozos/mod/auth/oauth2/syncdb"
  12. reg "imuslab.com/arozos/mod/auth/register"
  13. "imuslab.com/arozos/mod/common"
  14. db "imuslab.com/arozos/mod/database"
  15. permission "imuslab.com/arozos/mod/permission"
  16. "imuslab.com/arozos/mod/time/nightly"
  17. "imuslab.com/arozos/mod/user"
  18. )
  19. type ldapHandler struct {
  20. ag *auth.AuthAgent
  21. ldapreader *ldapreader.LdapReader
  22. reg *reg.RegisterHandler
  23. coredb *db.Database
  24. permissionHandler *permission.PermissionHandler
  25. userHandler *user.UserHandler
  26. iconSystem string
  27. syncdb *syncdb.SyncDB
  28. nightlyManager *nightly.TaskManager
  29. }
  30. type Config struct {
  31. Enabled bool `json:"enabled"`
  32. BindUsername string `json:"bind_username"`
  33. BindPassword string `json:"bind_password"`
  34. FQDN string `json:"fqdn"`
  35. BaseDN string `json:"base_dn"`
  36. }
  37. type UserAccount struct {
  38. Username string `json:"username"`
  39. Group []string `json:"group"`
  40. EquivGroup []string `json:"equiv_group"`
  41. }
  42. //syncorizeUserReturnInterface not designed to be used outside
  43. type syncorizeUserReturnInterface struct {
  44. Userinfo []UserAccount `json:"userinfo"`
  45. TotalLength int `json:"total_length"`
  46. Length int `json:"length"`
  47. Error string `json:"error"`
  48. }
  49. //NewLdapHandler xxx
  50. func NewLdapHandler(authAgent *auth.AuthAgent, register *reg.RegisterHandler, coreDb *db.Database, permissionHandler *permission.PermissionHandler, userHandler *user.UserHandler, nightlyManager *nightly.TaskManager, iconSystem string) *ldapHandler {
  51. //ldap handler init
  52. log.Println("Starting LDAP client...")
  53. err := coreDb.NewTable("ldap")
  54. if err != nil {
  55. log.Println("Failed to create LDAP database. Terminating.")
  56. panic(err)
  57. }
  58. //key value to be used for LDAP authentication
  59. BindUsername := readSingleConfig("BindUsername", coreDb)
  60. BindPassword := readSingleConfig("BindPassword", coreDb)
  61. FQDN := readSingleConfig("FQDN", coreDb)
  62. BaseDN := readSingleConfig("BaseDN", coreDb)
  63. LDAPHandler := ldapHandler{
  64. ag: authAgent,
  65. ldapreader: ldapreader.NewLDAPReader(BindUsername, BindPassword, FQDN, BaseDN),
  66. reg: register,
  67. coredb: coreDb,
  68. permissionHandler: permissionHandler,
  69. userHandler: userHandler,
  70. iconSystem: iconSystem,
  71. syncdb: syncdb.NewSyncDB(),
  72. nightlyManager: nightlyManager,
  73. }
  74. nightlyManager.RegisterNightlyTask(LDAPHandler.NightlySync)
  75. return &LDAPHandler
  76. }
  77. func (ldap *ldapHandler) ReadConfig(w http.ResponseWriter, r *http.Request) {
  78. //basic components
  79. enabled, err := strconv.ParseBool(ldap.readSingleConfig("enabled"))
  80. if err != nil {
  81. common.SendTextResponse(w, "Invalid config value [key=enabled].")
  82. return
  83. }
  84. //get the LDAP config from db
  85. BindUsername := ldap.readSingleConfig("BindUsername")
  86. BindPassword := ldap.readSingleConfig("BindPassword")
  87. FQDN := ldap.readSingleConfig("FQDN")
  88. BaseDN := ldap.readSingleConfig("BaseDN")
  89. //marshall it and return
  90. config, err := json.Marshal(Config{
  91. Enabled: enabled,
  92. BindUsername: BindUsername,
  93. BindPassword: BindPassword,
  94. FQDN: FQDN,
  95. BaseDN: BaseDN,
  96. })
  97. if err != nil {
  98. empty, err := json.Marshal(Config{})
  99. if err != nil {
  100. common.SendErrorResponse(w, "Error while marshalling config")
  101. }
  102. common.SendJSONResponse(w, string(empty))
  103. }
  104. common.SendJSONResponse(w, string(config))
  105. }
  106. func (ldap *ldapHandler) WriteConfig(w http.ResponseWriter, r *http.Request) {
  107. //receive the parameter
  108. enabled, err := common.Mv(r, "enabled", true)
  109. if err != nil {
  110. common.SendErrorResponse(w, "enabled field can't be empty")
  111. return
  112. }
  113. //allow empty fields if enabled is false
  114. showError := true
  115. if enabled != "true" {
  116. showError = false
  117. }
  118. //four fields to store the LDAP authentication information
  119. BindUsername, err := common.Mv(r, "bind_username", true)
  120. if err != nil {
  121. if showError {
  122. common.SendErrorResponse(w, "bind_username field can't be empty")
  123. return
  124. }
  125. }
  126. BindPassword, err := common.Mv(r, "bind_password", true)
  127. if err != nil {
  128. if showError {
  129. common.SendErrorResponse(w, "bind_password field can't be empty")
  130. return
  131. }
  132. }
  133. FQDN, err := common.Mv(r, "fqdn", true)
  134. if err != nil {
  135. if showError {
  136. common.SendErrorResponse(w, "fqdn field can't be empty")
  137. return
  138. }
  139. }
  140. BaseDN, err := common.Mv(r, "base_dn", true)
  141. if err != nil {
  142. if showError {
  143. common.SendErrorResponse(w, "base_dn field can't be empty")
  144. return
  145. }
  146. }
  147. //write the data back to db
  148. ldap.coredb.Write("ldap", "enabled", enabled)
  149. ldap.coredb.Write("ldap", "BindUsername", BindUsername)
  150. ldap.coredb.Write("ldap", "BindPassword", BindPassword)
  151. ldap.coredb.Write("ldap", "FQDN", FQDN)
  152. ldap.coredb.Write("ldap", "BaseDN", BaseDN)
  153. //update the new authencation infromation
  154. ldap.ldapreader = ldapreader.NewLDAPReader(BindUsername, BindPassword, FQDN, BaseDN)
  155. //return ok
  156. common.SendOK(w)
  157. }
  158. //@para limit: -1 means unlimited
  159. func (ldap *ldapHandler) getAllUser(limit int) ([]UserAccount, int, error) {
  160. //read the user account from ldap, if limit is -1 then it will read all USERS
  161. var accounts []UserAccount
  162. result, err := ldap.ldapreader.GetAllUser()
  163. if err != nil {
  164. return []UserAccount{}, 0, err
  165. }
  166. //loop through the result
  167. for i, v := range result {
  168. account := ldap.convertGroup(v)
  169. accounts = append(accounts, account)
  170. if i+1 > limit && limit != -1 {
  171. break
  172. }
  173. }
  174. //check if the return struct is empty, if yes then insert empty
  175. if len(accounts) > 0 {
  176. return accounts[1:], len(result), nil
  177. } else {
  178. return []UserAccount{}, 0, nil
  179. }
  180. }
  181. func (ldap *ldapHandler) convertGroup(ldapUser *ldap.Entry) UserAccount {
  182. //check the group belongs
  183. var Group []string
  184. var EquivGroup []string
  185. regexSyntax := regexp.MustCompile("cn=([^,]+),")
  186. for _, v := range ldapUser.GetAttributeValues("memberOf") {
  187. groups := regexSyntax.FindStringSubmatch(v)
  188. if len(groups) > 0 {
  189. //check if the LDAP group is already exists in ArOZOS system
  190. if ldap.permissionHandler.GroupExists(groups[1]) {
  191. EquivGroup = append(EquivGroup, groups[1])
  192. }
  193. //LDAP list
  194. Group = append(Group, groups[1])
  195. }
  196. }
  197. if len(EquivGroup) < 1 {
  198. if !ldap.permissionHandler.GroupExists(ldap.reg.GetDefaultUserGroup()) {
  199. //create new user group named default, prventing user don't have a group
  200. ldap.permissionHandler.NewPermissionGroup("default", false, 15<<30, []string{}, "Desktop")
  201. ldap.reg.SetDefaultUserGroup("default")
  202. }
  203. EquivGroup = append(EquivGroup, ldap.reg.GetDefaultUserGroup())
  204. }
  205. account := UserAccount{
  206. Username: ldapUser.GetAttributeValue("cn"),
  207. Group: Group,
  208. EquivGroup: EquivGroup,
  209. }
  210. return account
  211. }
  212. func (ldap *ldapHandler) TestConnection(w http.ResponseWriter, r *http.Request) {
  213. //marshall it and return the connection status
  214. userList, totalLength, err := ldap.getAllUser(10)
  215. if err != nil {
  216. errMessage, err := json.Marshal(syncorizeUserReturnInterface{Error: err.Error()})
  217. if err != nil {
  218. common.SendErrorResponse(w, "{\"error\":\"Error while marshalling information\"}")
  219. return
  220. }
  221. common.SendJSONResponse(w, string(errMessage))
  222. return
  223. }
  224. returnJSON := syncorizeUserReturnInterface{Userinfo: userList, Length: len(userList), TotalLength: totalLength, Error: ""}
  225. accountJSON, err := json.Marshal(returnJSON)
  226. if err != nil {
  227. errMessage, err := json.Marshal(syncorizeUserReturnInterface{Error: err.Error()})
  228. if err != nil {
  229. common.SendErrorResponse(w, "{\"error\":\"Error while marshalling information\"}")
  230. return
  231. }
  232. common.SendJSONResponse(w, string(errMessage))
  233. return
  234. }
  235. common.SendJSONResponse(w, string(accountJSON))
  236. }
  237. func (ldap *ldapHandler) checkCurrUserAdmin(w http.ResponseWriter, r *http.Request) bool {
  238. //check current user is admin and new update will remove it or not
  239. currentLoggedInUser, err := ldap.userHandler.GetUserInfoFromRequest(w, r)
  240. if err != nil {
  241. common.SendErrorResponse(w, "Error while getting user info")
  242. return false
  243. }
  244. ldapCurrUserInfo, err := ldap.ldapreader.GetUser(currentLoggedInUser.Username)
  245. if err != nil {
  246. common.SendErrorResponse(w, "Error while getting user info from LDAP")
  247. return false
  248. }
  249. isAdmin := false
  250. //get the croups out from LDAP group list
  251. regexSyntax := regexp.MustCompile("cn=([^,]+),")
  252. for _, v := range ldapCurrUserInfo.GetAttributeValues("memberOf") {
  253. //loop through all memberOf's array
  254. groups := regexSyntax.FindStringSubmatch(v)
  255. //if after regex there is still groups exists
  256. if len(groups) > 0 {
  257. //check if the LDAP group is already exists in ArOZOS system
  258. if ldap.permissionHandler.GroupExists(groups[1]) {
  259. if ldap.permissionHandler.GetPermissionGroupByName(groups[1]).IsAdmin {
  260. isAdmin = true
  261. }
  262. }
  263. }
  264. }
  265. return isAdmin
  266. }
  267. func (ldap *ldapHandler) SynchronizeUser(w http.ResponseWriter, r *http.Request) {
  268. //check if suer is admin before executing the command
  269. //if user is admin then check if user will lost him/her's admin access
  270. consistencyCheck := ldap.checkCurrUserAdmin(w, r)
  271. if !consistencyCheck {
  272. common.SendErrorResponse(w, "You will no longer become the admin after synchronizing, synchronize terminated")
  273. return
  274. }
  275. err := ldap.SynchronizeUserFromLDAP()
  276. if err != nil {
  277. common.SendErrorResponse(w, err.Error())
  278. return
  279. }
  280. common.SendOK(w)
  281. }
  282. func (ldap *ldapHandler) NightlySync() {
  283. err := ldap.SynchronizeUserFromLDAP()
  284. log.Println(err)
  285. }
  286. func (ldap *ldapHandler) SynchronizeUserFromLDAP() error {
  287. //check if suer is admin before executing the command
  288. //if user is admin then check if user will lost him/her's admin access
  289. ldapUsersList, _, err := ldap.getAllUser(-1)
  290. if err != nil {
  291. return err
  292. }
  293. for _, ldapUser := range ldapUsersList {
  294. //check if user exist in system
  295. if ldap.ag.UserExists(ldapUser.Username) {
  296. //if exists, then check if the user group is the same with ldap's setting
  297. //Get the permission groups by their ids
  298. userinfo, err := ldap.userHandler.GetUserInfoFromUsername(ldapUser.Username)
  299. if err != nil {
  300. return err
  301. }
  302. newPermissionGroups := ldap.permissionHandler.GetPermissionGroupByNameList(ldapUser.EquivGroup)
  303. //Set the user's permission to these groups
  304. userinfo.SetUserPermissionGroup(newPermissionGroups)
  305. }
  306. }
  307. return nil
  308. }
  309. //LOGIN related function
  310. //functions basically same as arozos's original function
  311. func (ldap *ldapHandler) HandleLoginPage(w http.ResponseWriter, r *http.Request) {
  312. checkLDAPenabled := ldap.readSingleConfig("enabled")
  313. if checkLDAPenabled == "false" {
  314. common.SendTextResponse(w, "LDAP not enabled.")
  315. return
  316. }
  317. //load the template from file and inject necessary variables
  318. red, _ := common.Mv(r, "redirect", false)
  319. //Append the redirection addr into the template
  320. imgsrc := "./web/" + ldap.iconSystem
  321. if !common.FileExists(imgsrc) {
  322. imgsrc = "./web/img/public/auth_icon.png"
  323. }
  324. imageBase64, _ := common.LoadImageAsBase64(imgsrc)
  325. parsedPage, err := common.Templateload("web/login.system", map[string]interface{}{
  326. "redirection_addr": red,
  327. "usercount": strconv.Itoa(ldap.ag.GetUserCounts()),
  328. "service_logo": imageBase64,
  329. "login_addr": "system/auth/ldap/login",
  330. })
  331. if err != nil {
  332. panic("Error. Unable to parse login page. Is web directory data exists?")
  333. }
  334. w.Header().Add("Content-Type", "text/html; charset=UTF-8")
  335. w.Write([]byte(parsedPage))
  336. }
  337. func (ldap *ldapHandler) HandleNewPasswordPage(w http.ResponseWriter, r *http.Request) {
  338. checkLDAPenabled := ldap.readSingleConfig("enabled")
  339. if checkLDAPenabled == "false" {
  340. common.SendTextResponse(w, "LDAP not enabled.")
  341. return
  342. }
  343. //get the parameter from the request
  344. acc, err := common.Mv(r, "username", false)
  345. if err != nil {
  346. common.SendErrorResponse(w, err.Error())
  347. return
  348. }
  349. displayname, err := common.Mv(r, "displayname", false)
  350. if err != nil {
  351. common.SendErrorResponse(w, err.Error())
  352. return
  353. }
  354. key, err := common.Mv(r, "authkey", false)
  355. if err != nil {
  356. common.SendErrorResponse(w, err.Error())
  357. return
  358. }
  359. //init the web interface
  360. imgsrc := "./web/" + ldap.iconSystem
  361. if !common.FileExists(imgsrc) {
  362. imgsrc = "./web/img/public/auth_icon.png"
  363. }
  364. imageBase64, _ := common.LoadImageAsBase64(imgsrc)
  365. template, err := common.Templateload("system/ldap/newPasswordTemplate.html", map[string]interface{}{
  366. "vendor_logo": imageBase64,
  367. "username": acc,
  368. "display_name": displayname,
  369. "key": key,
  370. })
  371. if err != nil {
  372. log.Fatal(err)
  373. }
  374. w.Header().Set("Content-Type", "text/html; charset=UTF-8")
  375. w.Write([]byte(template))
  376. }
  377. func (ldap *ldapHandler) HandleLogin(w http.ResponseWriter, r *http.Request) {
  378. checkLDAPenabled := ldap.readSingleConfig("enabled")
  379. if checkLDAPenabled == "false" {
  380. common.SendTextResponse(w, "LDAP not enabled.")
  381. return
  382. }
  383. //Get username from request using POST mode
  384. username, err := common.Mv(r, "username", true)
  385. if err != nil {
  386. //Username not defined
  387. log.Println("[System Auth] Someone trying to login with username: " + username)
  388. //Write to log
  389. ldap.ag.Logger.LogAuth(r, false)
  390. common.SendErrorResponse(w, "Username not defined or empty.")
  391. return
  392. }
  393. //Get password from request using POST mode
  394. password, err := common.Mv(r, "password", true)
  395. if err != nil {
  396. //Password not defined
  397. ldap.ag.Logger.LogAuth(r, false)
  398. common.SendErrorResponse(w, "Password not defined or empty.")
  399. return
  400. }
  401. //Get rememberme settings
  402. rememberme := false
  403. rmbme, _ := common.Mv(r, "rmbme", true)
  404. if rmbme == "true" {
  405. rememberme = true
  406. }
  407. //Check the database and see if this user is in the database
  408. passwordCorrect, err := ldap.ldapreader.Authenticate(username, password)
  409. if err != nil {
  410. ldap.ag.Logger.LogAuth(r, false)
  411. common.SendErrorResponse(w, "Unable to connect to LDAP server")
  412. log.Println("LDAP Authentication error, " + err.Error())
  413. return
  414. }
  415. //The database contain this user information. Check its password if it is correct
  416. if passwordCorrect {
  417. //Password correct
  418. //if user not exist then redirect to create pwd screen
  419. if !ldap.ag.UserExists(username) {
  420. authkey := ldap.syncdb.Store(username)
  421. common.SendJSONResponse(w, "{\"redirect\":\"system/auth/ldap/newPassword?username="+username+"&displayname="+username+"&authkey="+authkey+"\"}")
  422. } else {
  423. // Set user as authenticated
  424. ldap.ag.LoginUserByRequest(w, r, username, rememberme)
  425. //Print the login message to console
  426. log.Println(username + " logged in.")
  427. ldap.ag.Logger.LogAuth(r, true)
  428. common.SendOK(w)
  429. }
  430. } else {
  431. //Password incorrect
  432. log.Println(username + " has entered an invalid username or password")
  433. common.SendErrorResponse(w, "Invalid username or password")
  434. ldap.ag.Logger.LogAuth(r, false)
  435. return
  436. }
  437. }
  438. func (ldap *ldapHandler) HandleSetPassword(w http.ResponseWriter, r *http.Request) {
  439. checkLDAPenabled := ldap.readSingleConfig("enabled")
  440. if checkLDAPenabled == "false" {
  441. common.SendTextResponse(w, "LDAP not enabled.")
  442. return
  443. }
  444. //get paramters from request
  445. username, err := common.Mv(r, "username", true)
  446. if err != nil {
  447. common.SendErrorResponse(w, err.Error())
  448. return
  449. }
  450. password, err := common.Mv(r, "password", true)
  451. if err != nil {
  452. common.SendErrorResponse(w, err.Error())
  453. return
  454. }
  455. authkey, err := common.Mv(r, "authkey", true)
  456. if err != nil {
  457. common.SendErrorResponse(w, err.Error())
  458. return
  459. }
  460. //check if the input key matches the database's username
  461. isValid := ldap.syncdb.Read(authkey) == username
  462. ldap.syncdb.Delete(authkey) // remove the key, aka key is one time use only
  463. //if db data match the username, proceed
  464. if isValid {
  465. //if not exists
  466. if !ldap.ag.UserExists(username) {
  467. //get the user from ldap server
  468. ldapUser, err := ldap.ldapreader.GetUser(username)
  469. if err != nil {
  470. common.SendErrorResponse(w, err.Error())
  471. return
  472. }
  473. //convert the ldap usergroup to arozos usergroup
  474. convertedInfo := ldap.convertGroup(ldapUser)
  475. //create user account and login
  476. ldap.ag.CreateUserAccount(username, password, convertedInfo.EquivGroup)
  477. ldap.ag.Logger.LogAuth(r, true)
  478. ldap.ag.LoginUserByRequest(w, r, username, false)
  479. common.SendOK(w)
  480. return
  481. } else {
  482. //if exist then return error
  483. common.SendErrorResponse(w, "User exists, please contact the system administrator if you believe this is an error.")
  484. return
  485. }
  486. } else {
  487. common.SendErrorResponse(w, "Improper key detected")
  488. log.Println(r.RemoteAddr + " attempted to use invaild key to create new user but failed")
  489. return
  490. }
  491. }
  492. //CheckOAuth check if oauth is enabled
  493. func (ldap *ldapHandler) HandleCheckLDAP(w http.ResponseWriter, r *http.Request) {
  494. enabledB := false
  495. enabled := ldap.readSingleConfig("enabled")
  496. if enabled == "true" {
  497. enabledB = true
  498. }
  499. type returnFormat struct {
  500. Enabled bool `json:"enabled"`
  501. }
  502. json, err := json.Marshal(returnFormat{Enabled: enabledB})
  503. if err != nil {
  504. common.SendErrorResponse(w, "Error occurred while marshalling JSON response")
  505. }
  506. common.SendJSONResponse(w, string(json))
  507. }