123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- package ldap
- import (
- "encoding/json"
- "log"
- "net/http"
- "regexp"
- "strconv"
- "github.com/go-ldap/ldap"
- auth "imuslab.com/arozos/mod/auth"
- "imuslab.com/arozos/mod/auth/ldap/ldapreader"
- "imuslab.com/arozos/mod/auth/oauth2/syncdb"
- 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
- syncdb *syncdb.SyncDB
- }
- 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,
- syncdb: syncdb.NewSyncDB(),
- }
- 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 {
- account := ldap.convertGroup(v)
- accounts = append(accounts, account)
- if i > limit && limit != -1 {
- break
- }
- }
- if len(accounts) > 0 {
- return accounts[1:]
- } else {
- return []UserAccount{}
- }
- }
- func (ldap *ldapHandler) convertGroup(ldapUser *ldap.Entry) UserAccount {
- //check the group belongs
- var Group []string
- var EquivGroup []string
- regexSyntax := regexp.MustCompile("cn=([^,]+),")
- for _, v := range ldapUser.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: ldapUser.GetAttributeValue("cn"),
- Group: Group,
- EquivGroup: EquivGroup,
- }
- return account
- }
- 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/auth/ldap/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) HandleNewPasswordPage(w http.ResponseWriter, r *http.Request) {
- acc, err := common.Mv(r, "username", false)
- if err != nil {
- common.SendErrorResponse(w, err.Error())
- return
- }
- displayname, err := common.Mv(r, "displayname", false)
- if err != nil {
- common.SendErrorResponse(w, err.Error())
- return
- }
- key, err := common.Mv(r, "authkey", false)
- if err != nil {
- common.SendErrorResponse(w, err.Error())
- return
- }
- imgsrc := "./web/" + ldap.iconSystem
- if !common.FileExists(imgsrc) {
- imgsrc = "./web/img/public/auth_icon.png"
- }
- imageBase64, _ := common.LoadImageAsBase64(imgsrc)
- template, err := common.Templateload("system/ldap/newPasswordTemplate.html", map[string]interface{}{
- "vendor_logo": imageBase64,
- "username": acc,
- "display_name": displayname,
- "key": key,
- })
- if err != nil {
- log.Fatal(err)
- }
- w.Header().Set("Content-Type", "text/html; charset=UTF-8")
- w.Write([]byte(template))
- }
- func (ldap *ldapHandler) HandleLogin(w http.ResponseWriter, r *http.Request) {
- //Get username from request using POST mode
- username, err := common.Mv(r, "username", true)
- if err != nil {
- //Username not defined
- log.Println("[System Auth] Someone trying to login with username: " + username)
- //Write to log
- ldap.ag.Logger.LogAuth(r, false)
- common.SendErrorResponse(w, "Username not defined or empty.")
- return
- }
- //Get password from request using POST mode
- password, err := common.Mv(r, "password", true)
- if err != nil {
- //Password not defined
- ldap.ag.Logger.LogAuth(r, false)
- common.SendErrorResponse(w, "Password not defined or empty.")
- return
- }
- //Get rememberme settings
- rememberme := false
- rmbme, _ := common.Mv(r, "rmbme", true)
- if rmbme == "true" {
- rememberme = true
- }
- //Check the database and see if this user is in the database
- passwordCorrect, err := ldap.ldapreader.Authenticate(username, password)
- if err != nil {
- ldap.ag.Logger.LogAuth(r, false)
- common.SendErrorResponse(w, "Unable to connect to LDAP server")
- log.Println("LDAP Authentication error, " + err.Error())
- return
- }
- //The database contain this user information. Check its password if it is correct
- if passwordCorrect {
- //Password correct
- if !ldap.ag.UserExists(username) {
- authkey := ldap.syncdb.Store(username)
- common.SendErrorResponse(w, "Redirection=system/auth/ldap/newPassword?username="+username+"&displayname="+username+"&authkey="+authkey)
- } else {
- // Set user as authenticated
- ldap.ag.LoginUserByRequest(w, r, username, rememberme)
- //Print the login message to console
- log.Println(username + " logged in.")
- ldap.ag.Logger.LogAuth(r, true)
- common.SendOK(w)
- }
- } else {
- //Password incorrect
- log.Println(username + " has entered an invalid username or password")
- common.SendErrorResponse(w, "Invalid username or password")
- ldap.ag.Logger.LogAuth(r, false)
- return
- }
- }
- func (ldap *ldapHandler) HandleSetPassword(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
- }
- authkey, err := common.Mv(r, "authkey", true)
- if err != nil {
- common.SendErrorResponse(w, err.Error())
- return
- }
- //check if the input key matches the database's username
- isValid := ldap.syncdb.Read(authkey) == username
- ldap.syncdb.Delete(authkey) // remove the key
- if isValid {
- if !ldap.ag.UserExists(username) {
- ldapUser, err := ldap.ldapreader.GetUser(username)
- if err != nil {
- common.SendErrorResponse(w, err.Error())
- return
- }
- convertedInfo := ldap.convertGroup(ldapUser)
- ldap.ag.CreateUserAccount(username, password, convertedInfo.Group)
- common.SendOK(w)
- return
- } else {
- common.SendErrorResponse(w, "User existed!!")
- return
- }
- } else {
- common.SendErrorResponse(w, "Improper key detected")
- log.Println(r.RemoteAddr + " attempted to use invaild key to create new user but failed")
- return
- }
- }
|