ldap.go 14 KB

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