package ldap import ( "encoding/json" "log" "net/http" "regexp" "strconv" auth "imuslab.com/arozos/mod/auth" "imuslab.com/arozos/mod/auth/ldap/ldapreader" reg "imuslab.com/arozos/mod/auth/register" "imuslab.com/arozos/mod/common" db "imuslab.com/arozos/mod/database" permission "imuslab.com/arozos/mod/permission" "imuslab.com/arozos/mod/user" ) type ldapHandler struct { ag *auth.AuthAgent ldapreader *ldapreader.LdapReader reg *reg.RegisterHandler coredb *db.Database permissionHandler *permission.PermissionHandler userHandler *user.UserHandler iconSystem string } type Config struct { Enabled bool `json:"enabled"` AutoRedirect bool `json:"auto_redirect"` BindUsername string `json:"bind_username"` BindPassword string `json:"bind_password"` FQDN string `json:"fqdn"` BaseDN string `json:"base_dn"` } type UserAccount struct { Username string `json:"username"` Group []string `json:"group"` EquivGroup []string `json:"equiv_group"` } /* TODO: not sure why auto redirect will keep enable TODO: stop user to syncorize if the current user will lost admin access */ //NewLdapHandler xxx func NewLdapHandler(authAgent *auth.AuthAgent, register *reg.RegisterHandler, coreDb *db.Database, permissionHandler *permission.PermissionHandler, userHandler *user.UserHandler, iconSystem string) *ldapHandler { //ldap handler init log.Println("Starting LDAP client...") err := coreDb.NewTable("ldap") if err != nil { log.Println("Failed to create LDAP database. Terminating.") panic(err) } //key value to be used for LDAP authentication BindUsername := readSingleConfig("BindUsername", coreDb) BindPassword := readSingleConfig("BindPassword", coreDb) FQDN := readSingleConfig("FQDN", coreDb) BaseDN := readSingleConfig("BaseDN", coreDb) LDAPHandler := ldapHandler{ ag: authAgent, ldapreader: ldapreader.NewLDAPReader(BindUsername, BindPassword, FQDN, BaseDN), reg: register, coredb: coreDb, permissionHandler: permissionHandler, userHandler: userHandler, iconSystem: iconSystem, } return &LDAPHandler } func (ldap *ldapHandler) ReadConfig(w http.ResponseWriter, r *http.Request) { //basic components enabled, err := strconv.ParseBool(ldap.readSingleConfig("enabled")) if err != nil { common.SendTextResponse(w, "Invalid config value [key=enabled].") return } autoredirect, err := strconv.ParseBool(ldap.readSingleConfig("autoredirect")) if err != nil { common.SendTextResponse(w, "Invalid config value [key=autoredirect].") return } //get the LDAP config from db BindUsername := ldap.readSingleConfig("BindUsername") BindPassword := ldap.readSingleConfig("BindPassword") FQDN := ldap.readSingleConfig("FQDN") BaseDN := ldap.readSingleConfig("BaseDN") //marshall it and return config, err := json.Marshal(Config{ Enabled: enabled, AutoRedirect: autoredirect, BindUsername: BindUsername, BindPassword: BindPassword, FQDN: FQDN, BaseDN: BaseDN, }) if err != nil { empty, err := json.Marshal(Config{}) if err != nil { common.SendErrorResponse(w, "Error while marshalling config") } common.SendJSONResponse(w, string(empty)) } common.SendJSONResponse(w, string(config)) } func (ldap *ldapHandler) WriteConfig(w http.ResponseWriter, r *http.Request) { enabled, err := common.Mv(r, "enabled", true) if err != nil { common.SendErrorResponse(w, "enabled field can't be empty") return } autoredirect, err := common.Mv(r, "autoredirect", true) if err != nil { common.SendErrorResponse(w, "enabled field can't be empty") return } //allow empty fields if enabled is false showError := true if enabled != "true" { showError = false } //four fields to store the LDAP authentication information BindUsername, err := common.Mv(r, "bind_username", true) if err != nil { if showError { common.SendErrorResponse(w, "bind_username field can't be empty") return } } BindPassword, err := common.Mv(r, "bind_password", true) if err != nil { if showError { common.SendErrorResponse(w, "bind_password field can't be empty") return } } FQDN, err := common.Mv(r, "fqdn", true) if err != nil { if showError { common.SendErrorResponse(w, "fqdn field can't be empty") return } } BaseDN, err := common.Mv(r, "base_dn", true) if err != nil { if showError { common.SendErrorResponse(w, "base_dn field can't be empty") return } } ldap.coredb.Write("ldap", "enabled", enabled) ldap.coredb.Write("ldap", "autoredirect", autoredirect) ldap.coredb.Write("ldap", "BindUsername", BindUsername) ldap.coredb.Write("ldap", "BindPassword", BindPassword) ldap.coredb.Write("ldap", "FQDN", FQDN) ldap.coredb.Write("ldap", "BaseDN", BaseDN) //update the new authencation infromation ldap.ldapreader = ldapreader.NewLDAPReader(BindUsername, BindPassword, FQDN, BaseDN) common.SendOK(w) } //@para limit: -1 means unlimited func (ldap *ldapHandler) getAllUser(limit int) []UserAccount { var accounts []UserAccount result, _ := ldap.ldapreader.GetAllUser() //loop through the result for i, v := range result { //check the group belongs var Group []string var EquivGroup []string regexSyntax := regexp.MustCompile("cn=([^,]+),") for _, v := range v.GetAttributeValues("memberOf") { groups := regexSyntax.FindStringSubmatch(v) if len(groups) > 0 { //check if the LDAP group is already exists in ArOZOS system if ldap.permissionHandler.GroupExists(groups[1]) { EquivGroup = append(EquivGroup, groups[1]) } //LDAP list Group = append(Group, groups[1]) } } if len(EquivGroup) < 1 { EquivGroup = append(EquivGroup, ldap.reg.DefaultUserGroup) } account := UserAccount{ Username: v.GetAttributeValue("cn"), Group: Group, EquivGroup: EquivGroup, } accounts = append(accounts, account) if i > limit && limit != -1 { break } } if len(accounts) > 0 { return accounts[1:] } else { return []UserAccount{} } } func (ldap *ldapHandler) TestConnection(w http.ResponseWriter, r *http.Request) { //marshall it and return accountJSON, err := json.Marshal(ldap.getAllUser(10)) if err != nil { empty, err := json.Marshal(UserAccount{}) if err != nil { common.SendErrorResponse(w, "Error while marshalling information") } common.SendJSONResponse(w, string(empty)) } common.SendJSONResponse(w, string(accountJSON)) } func (ldap *ldapHandler) checkCurrUserAdmin(w http.ResponseWriter, r *http.Request) bool { //check current user is admin and new update will remove it or not currentLoggedInUser, err := ldap.userHandler.GetUserInfoFromRequest(w, r) if err != nil { common.SendErrorResponse(w, "Error while getting user info") return false } ldapCurrUserInfo, err := ldap.ldapreader.GetUser(currentLoggedInUser.Username) if err != nil { common.SendErrorResponse(w, "Error while getting user info from LDAP") return false } isAdmin := false regexSyntax := regexp.MustCompile("cn=([^,]+),") for _, v := range ldapCurrUserInfo.GetAttributeValues("memberOf") { groups := regexSyntax.FindStringSubmatch(v) if len(groups) > 0 { //check if the LDAP group is already exists in ArOZOS system if ldap.permissionHandler.GroupExists(groups[1]) { if ldap.permissionHandler.GetPermissionGroupByName(groups[1]).IsAdmin { isAdmin = true } } } } return isAdmin } func (ldap *ldapHandler) SynchronizeUser(w http.ResponseWriter, r *http.Request) { consistencyCheck := ldap.checkCurrUserAdmin(w, r) if !consistencyCheck { common.SendErrorResponse(w, "You will no longer become the admin after synchronizing, synchronize terminated") return } ldapUsersList := ldap.getAllUser(-1) for _, ldapUser := range ldapUsersList { //check if user does not exist in system if !ldap.ag.UserExists(ldapUser.Username) { //TODO change password ldap.ag.CreateUserAccount(ldapUser.Username, "P@ssw0rd", ldapUser.EquivGroup) } else { //if exists, then check if the user group is the same with ldap's setting //Get the permission groups by their ids userinfo, err := ldap.userHandler.GetUserInfoFromUsername(ldapUser.Username) if err != nil { common.SendErrorResponse(w, "Error while getting user info") return } newPermissionGroups := ldap.permissionHandler.GetPermissionGroupByNameList(ldapUser.EquivGroup) //Set the user's permission to these groups userinfo.SetUserPermissionGroup(newPermissionGroups) } } common.SendOK(w) } //LOGIN related command func (ldap *ldapHandler) HandleLoginPage(w http.ResponseWriter, r *http.Request) { red, _ := common.Mv(r, "redirect", false) //Append the redirection addr into the template imgsrc := "./web/" + ldap.iconSystem if !common.FileExists(imgsrc) { imgsrc = "./web/img/public/auth_icon.png" } imageBase64, _ := common.LoadImageAsBase64(imgsrc) parsedPage, err := common.Templateload("web/login.system", map[string]interface{}{ "redirection_addr": red, "usercount": strconv.Itoa(ldap.ag.GetUserCounts()), "service_logo": imageBase64, "login_addr": "system/ldap/auth/login", }) if err != nil { panic("Error. Unable to parse login page. Is web directory data exists?") } w.Header().Add("Content-Type", "text/html; charset=UTF-8") w.Write([]byte(parsedPage)) } func (ldap *ldapHandler) HandleLogin(w http.ResponseWriter, r *http.Request) { username, err := common.Mv(r, "username", true) if err != nil { common.SendErrorResponse(w, err.Error()) return } password, err := common.Mv(r, "password", true) if err != nil { common.SendErrorResponse(w, err.Error()) return } loginInfo, err := ldap.ldapreader.Authenticate(username, password) //authencate successful //common }