浏览代码

Merge branch 'ldap-2022Feb19' of tmp/arozos into master

LGTM
TC 3 年之前
父节点
当前提交
026ae12450

+ 4 - 0
go.mod

@@ -13,6 +13,9 @@ require (
 	github.com/frankban/quicktest v1.10.0 // indirect
 	github.com/frankban/quicktest v1.10.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.1.0
 	github.com/gabriel-vasile/mimetype v1.1.0
 	github.com/go-git/go-git/v5 v5.2.0
 	github.com/go-git/go-git/v5 v5.2.0
+	github.com/go-ldap/ldap v3.0.3+incompatible // indirect
+	github.com/go-ldap/ldap/v3 v3.4.2 // indirect
+	github.com/google/uuid v1.3.0 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96 // indirect
 	github.com/gorilla/sessions v1.2.0
 	github.com/gorilla/sessions v1.2.0
 	github.com/gorilla/websocket v1.4.2
 	github.com/gorilla/websocket v1.4.2
@@ -41,6 +44,7 @@ require (
 	golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1
 	golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 // indirect
 	golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 // indirect
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
 	gopkg.in/sourcemap.v1 v1.0.5 // indirect
 	gopkg.in/sourcemap.v1 v1.0.5 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
 )

+ 13 - 0
go.sum

@@ -39,6 +39,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
+github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
@@ -146,6 +148,8 @@ github.com/gabriel-vasile/mimetype v1.1.0/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pm
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
+github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
 github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
@@ -161,6 +165,10 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
 github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
+github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
+github.com/go-ldap/ldap/v3 v3.4.2 h1:zFZKcXKLqZpFMrMQGHeHWKXbDTdNCmhGY9AK41zPh+8=
+github.com/go-ldap/ldap/v3 v3.4.2/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
@@ -244,6 +252,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -544,6 +554,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
@@ -906,6 +917,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 43 - 0
ldap.go

@@ -0,0 +1,43 @@
+package main
+
+import (
+	"net/http"
+
+	ldap "imuslab.com/arozos/mod/auth/ldap"
+	prout "imuslab.com/arozos/mod/prouter"
+)
+
+func ldapInit() {
+	//ldap
+	ldapHandler := ldap.NewLdapHandler(authAgent, registerHandler, sysdb, permissionHandler, userHandler, nightlyManager, iconSystem)
+
+	//add a entry to the system settings
+	adminRouter := prout.NewModuleRouter(prout.RouterOption{
+		ModuleName:  "System Setting",
+		AdminOnly:   true,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			errorHandlePermissionDenied(w, r)
+		},
+	})
+	registerSetting(settingModule{
+		Name:         "LDAP<sup>BETA</sup>",
+		Desc:         "Allows external account access to system",
+		IconPath:     "SystemAO/advance/img/small_icon.png",
+		Group:        "Security",
+		StartDir:     "SystemAO/advance/ldap.html",
+		RequireAdmin: true,
+	})
+
+	adminRouter.HandleFunc("/system/auth/ldap/config/read", ldapHandler.ReadConfig)
+	adminRouter.HandleFunc("/system/auth/ldap/config/write", ldapHandler.WriteConfig)
+	adminRouter.HandleFunc("/system/auth/ldap/config/testConnection", ldapHandler.TestConnection)
+	adminRouter.HandleFunc("/system/auth/ldap/config/syncorizeUser", ldapHandler.SynchronizeUser)
+
+	//login interface and login handler
+	http.HandleFunc("/system/auth/ldap/login", ldapHandler.HandleLogin)
+	http.HandleFunc("/system/auth/ldap/setPassword", ldapHandler.HandleSetPassword)
+	http.HandleFunc("/system/auth/ldap/newPassword", ldapHandler.HandleNewPasswordPage)
+	http.HandleFunc("/ldapLogin.system", ldapHandler.HandleLoginPage)
+	http.HandleFunc("/system/auth/ldap/checkldap", ldapHandler.HandleCheckLDAP)
+}

+ 2 - 1
main.router.go

@@ -37,10 +37,11 @@ func mrouter(h http.Handler) http.Handler {
 				imgsrc = "./web/img/public/auth_icon.png"
 				imgsrc = "./web/img/public/auth_icon.png"
 			}
 			}
 			imageBase64, _ := LoadImageAsBase64(imgsrc)
 			imageBase64, _ := LoadImageAsBase64(imgsrc)
-			parsedPage, err := template_load("web/login.system", map[string]interface{}{
+			parsedPage, err := common.Templateload("web/login.system", map[string]interface{}{
 				"redirection_addr": red,
 				"redirection_addr": red,
 				"usercount":        strconv.Itoa(authAgent.GetUserCounts()),
 				"usercount":        strconv.Itoa(authAgent.GetUserCounts()),
 				"service_logo":     imageBase64,
 				"service_logo":     imageBase64,
+				"login_addr":       "system/auth/login",
 			})
 			})
 			if err != nil {
 			if err != nil {
 				panic("Error. Unable to parse login page. Is web directory data exists?")
 				panic("Error. Unable to parse login page. Is web directory data exists?")

+ 21 - 0
mod/auth/ldap/common.go

@@ -0,0 +1,21 @@
+package ldap
+
+import db "imuslab.com/arozos/mod/database"
+
+func readSingleConfig(key string, coredb *db.Database) string {
+	var value string
+	err := coredb.Read("ldap", key, &value)
+	if err != nil {
+		value = ""
+	}
+	return value
+}
+
+func (ldap *ldapHandler) readSingleConfig(key string) string {
+	var value string
+	err := ldap.coredb.Read("ldap", key, &value)
+	if err != nil {
+		value = ""
+	}
+	return value
+}

+ 170 - 0
mod/auth/ldap/ldap.go

@@ -0,0 +1,170 @@
+package ldap
+
+import (
+	"log"
+	"regexp"
+
+	"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"
+	db "imuslab.com/arozos/mod/database"
+	permission "imuslab.com/arozos/mod/permission"
+	"imuslab.com/arozos/mod/time/nightly"
+	"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
+	nightlyManager    *nightly.TaskManager
+}
+
+type Config struct {
+	Enabled      bool   `json:"enabled"`
+	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"`
+}
+
+//syncorizeUserReturnInterface not designed to be used outside
+type syncorizeUserReturnInterface struct {
+	Userinfo    []UserAccount `json:"userinfo"`
+	TotalLength int           `json:"total_length"`
+	Length      int           `json:"length"`
+	Error       string        `json:"error"`
+}
+
+//NewLdapHandler xxx
+func NewLdapHandler(authAgent *auth.AuthAgent, register *reg.RegisterHandler, coreDb *db.Database, permissionHandler *permission.PermissionHandler, userHandler *user.UserHandler, nightlyManager *nightly.TaskManager, 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(),
+		nightlyManager:    nightlyManager,
+	}
+
+	nightlyManager.RegisterNightlyTask(LDAPHandler.NightlySync)
+
+	return &LDAPHandler
+}
+
+//@para limit: -1 means unlimited
+func (ldap *ldapHandler) getAllUser(limit int) ([]UserAccount, int, error) {
+	//read the user account from ldap, if limit is -1 then it will read all USERS
+	var accounts []UserAccount
+	result, err := ldap.ldapreader.GetAllUser()
+	if err != nil {
+		return []UserAccount{}, 0, err
+	}
+	//loop through the result
+	for i, v := range result {
+		account := ldap.convertGroup(v)
+		accounts = append(accounts, account)
+		if i+1 > limit && limit != -1 {
+			break
+		}
+	}
+	//check if the return struct is empty, if yes then insert empty
+	if len(accounts) > 0 {
+		return accounts[1:], len(result), nil
+	} else {
+		return []UserAccount{}, 0, nil
+	}
+}
+
+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 {
+		if !ldap.permissionHandler.GroupExists(ldap.reg.GetDefaultUserGroup()) {
+			//create new user group named default, prventing user don't have a group
+			ldap.permissionHandler.NewPermissionGroup("default", false, 15<<30, []string{}, "Desktop")
+			ldap.reg.SetDefaultUserGroup("default")
+		}
+		EquivGroup = append(EquivGroup, ldap.reg.GetDefaultUserGroup())
+	}
+	account := UserAccount{
+		Username:   ldapUser.GetAttributeValue("cn"),
+		Group:      Group,
+		EquivGroup: EquivGroup,
+	}
+	return account
+}
+
+func (ldap *ldapHandler) NightlySync() {
+	err := ldap.SynchronizeUserFromLDAP()
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+func (ldap *ldapHandler) SynchronizeUserFromLDAP() error {
+	//check if suer is admin before executing the command
+	//if user is admin then check if user will lost him/her's admin access
+	ldapUsersList, _, err := ldap.getAllUser(-1)
+	if err != nil {
+		return err
+	}
+	for _, ldapUser := range ldapUsersList {
+		//check if user exist in system
+		if ldap.ag.UserExists(ldapUser.Username) {
+			//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 {
+				return err
+			}
+			newPermissionGroups := ldap.permissionHandler.GetPermissionGroupByNameList(ldapUser.EquivGroup)
+			//Set the user's permission to these groups
+			userinfo.SetUserPermissionGroup(newPermissionGroups)
+		}
+	}
+	return nil
+}

+ 102 - 0
mod/auth/ldap/ldapreader/reader.go

@@ -0,0 +1,102 @@
+package ldapreader
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/go-ldap/ldap"
+)
+
+type LdapReader struct {
+	username string
+	password string
+	server   string
+	basedn   string
+}
+
+//NewOauthHandler xxx
+func NewLDAPReader(username string, password string, server string, basedn string) *LdapReader {
+
+	LDAPHandler := LdapReader{
+		username: username,
+		password: password,
+		server:   server,
+		basedn:   basedn,
+	}
+
+	return &LDAPHandler
+}
+
+func (handler *LdapReader) GetUser(username string) (*ldap.Entry, error) {
+	returnVal, err := handler.retrieveInformation("uid="+username+","+handler.basedn, "(objectClass=*)", ldap.ScopeBaseObject, handler.username, handler.password)
+	if err != nil {
+		return nil, err
+	}
+	if len(returnVal) == 0 {
+		return nil, fmt.Errorf("nothing found for user %s", username)
+	}
+	return returnVal[0], nil
+}
+
+func (handler *LdapReader) GetAllUser() ([]*ldap.Entry, error) {
+	return handler.retrieveInformation(handler.basedn, "(objectClass=*)", ldap.ScopeWholeSubtree, handler.username, handler.password)
+}
+
+func (handler *LdapReader) Authenticate(username string, password string) (bool, error) {
+	userInformation, err := handler.retrieveInformation("uid="+username+","+handler.basedn, "(objectClass=person)", ldap.ScopeBaseObject, "uid="+username+","+handler.basedn, password)
+	if err != nil {
+		if strings.Contains(err.Error(), "LDAP Result Code 32") {
+			return false, nil
+		}
+		if strings.Contains(err.Error(), "LDAP Result Code 53") {
+			return false, nil
+		}
+		if strings.Contains(err.Error(), "Couldn't fetch search entries") {
+			return false, nil
+		}
+		return false, err
+	}
+	if len(userInformation) > 0 {
+		if userInformation[0].GetAttributeValue("cn") == username {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+func (handler *LdapReader) retrieveInformation(dn string, filter string, typeOfSearch int, username string, password string) ([]*ldap.Entry, error) {
+	ldapURL, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", handler.server))
+	if err != nil {
+		return nil, err
+	}
+	defer ldapURL.Close()
+
+	ldapURL.Bind(username, password)
+	searchReq := ldap.NewSearchRequest(
+		dn,
+		typeOfSearch,
+		ldap.NeverDerefAliases,
+		0,
+		0,
+		false,
+		filter,
+		[]string{"uid", "memberOf", "cn", "sAMAccountName"},
+		//[]string{},
+		nil,
+	)
+	result, err := ldapURL.Search(searchReq)
+	/*
+		if err == nil {
+			result.PrettyPrint(4)
+		}
+	*/
+	if err != nil {
+		return nil, fmt.Errorf("Search Error: %s", err)
+	}
+
+	if len(result.Entries) > 0 {
+		return result.Entries, nil
+	} else {
+		return nil, fmt.Errorf("Couldn't fetch search entries")
+	}
+}

+ 79 - 0
mod/auth/ldap/syncdb/syncdb.go

@@ -0,0 +1,79 @@
+package syncdb
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	uuid "github.com/satori/go.uuid"
+)
+
+type SyncDB struct {
+	db *sync.Map //HERE ALSO CHANGED, USE POINTER INSTEAD OF A COPY OF THE ORIGINAL SYNCNAMP
+}
+
+type dbStructure struct {
+	timestamp time.Time
+	value     string
+}
+
+func NewSyncDB() *SyncDB {
+	//Create a new SyncMap for this SyncDB Object
+	newDB := sync.Map{}
+	//Put the newly craeted syncmap into the db object
+	newSyncDB := SyncDB{db: &newDB} //!!! USE POINTER HERE INSTEAD OF THE SYNC MAP ITSELF
+	//Return the pointer of the new SyncDB object
+	newSyncDB.AutoCleaning()
+	return &newSyncDB
+}
+
+func (p SyncDB) AutoCleaning() {
+	//create the routine for auto clean trash
+	go func() {
+		for {
+			<-time.After(5 * 60 * time.Second) //no rush, clean every five minute
+			p.db.Range(func(key, value interface{}) bool {
+				if time.Now().Sub(value.(dbStructure).timestamp).Minutes() >= 30 {
+					p.db.Delete(key)
+				}
+				return true
+			})
+		}
+	}()
+}
+
+func (p SyncDB) Store(value string) string {
+	uid := uuid.NewV4().String()
+	NewField := dbStructure{
+		timestamp: time.Now(),
+		value:     value,
+	}
+	p.db.Store(uid, NewField)
+	return uid
+}
+
+func (p SyncDB) Read(uuid string) string {
+	value, ok := p.db.Load(uuid)
+	if !ok {
+		return ""
+	} else {
+		return value.(dbStructure).value
+	}
+}
+
+func (p SyncDB) Delete(uuid string) {
+	p.db.Delete(uuid)
+}
+
+func (p SyncDB) ToString() {
+	p.db.Range(func(key, value interface{}) bool {
+		fmt.Print(key)
+		fmt.Print(" : ")
+		fmt.Println(value.(dbStructure).value)
+		fmt.Print(" @ ")
+		//fmt.Print(value.(dbStructure).timestamp)
+		fmt.Print(time.Now().Sub(value.(dbStructure).timestamp).Seconds())
+		fmt.Print("\n")
+		return true
+	})
+}

+ 174 - 0
mod/auth/ldap/web_admin.go

@@ -0,0 +1,174 @@
+package ldap
+
+import (
+	"encoding/json"
+	"net/http"
+	"regexp"
+	"strconv"
+
+	"imuslab.com/arozos/mod/auth/ldap/ldapreader"
+	"imuslab.com/arozos/mod/common"
+)
+
+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
+	}
+	//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,
+		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) {
+	//receive the parameter
+	enabled, err := common.Mv(r, "enabled", 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
+		}
+	}
+
+	//write the data back to db
+	ldap.coredb.Write("ldap", "enabled", enabled)
+	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)
+
+	//return ok
+	common.SendOK(w)
+}
+
+func (ldap *ldapHandler) TestConnection(w http.ResponseWriter, r *http.Request) {
+	//marshall it and return the connection status
+	userList, totalLength, err := ldap.getAllUser(10)
+	if err != nil {
+		errMessage, err := json.Marshal(syncorizeUserReturnInterface{Error: err.Error()})
+		if err != nil {
+			common.SendErrorResponse(w, "{\"error\":\"Error while marshalling information\"}")
+			return
+		}
+		common.SendJSONResponse(w, string(errMessage))
+		return
+	}
+	returnJSON := syncorizeUserReturnInterface{Userinfo: userList, Length: len(userList), TotalLength: totalLength, Error: ""}
+	accountJSON, err := json.Marshal(returnJSON)
+	if err != nil {
+		errMessage, err := json.Marshal(syncorizeUserReturnInterface{Error: err.Error()})
+		if err != nil {
+			common.SendErrorResponse(w, "{\"error\":\"Error while marshalling information\"}")
+			return
+		}
+		common.SendJSONResponse(w, string(errMessage))
+		return
+	}
+	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
+	//get the croups out from LDAP group list
+	regexSyntax := regexp.MustCompile("cn=([^,]+),")
+	for _, v := range ldapCurrUserInfo.GetAttributeValues("memberOf") {
+		//loop through all memberOf's array
+		groups := regexSyntax.FindStringSubmatch(v)
+		//if after regex there is still groups exists
+		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) {
+	//check if suer is admin before executing the command
+	//if user is admin then check if user will lost him/her's admin access
+	consistencyCheck := ldap.checkCurrUserAdmin(w, r)
+	if !consistencyCheck {
+		common.SendErrorResponse(w, "You will no longer become the admin after synchronizing, synchronize terminated")
+		return
+	}
+
+	err := ldap.SynchronizeUserFromLDAP()
+	if err != nil {
+		common.SendErrorResponse(w, err.Error())
+		return
+	}
+	common.SendOK(w)
+}

+ 220 - 0
mod/auth/ldap/web_login.go

@@ -0,0 +1,220 @@
+package ldap
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"strconv"
+
+	"imuslab.com/arozos/mod/common"
+)
+
+//LOGIN related function
+//functions basically same as arozos's original function
+func (ldap *ldapHandler) HandleLoginPage(w http.ResponseWriter, r *http.Request) {
+	checkLDAPenabled := ldap.readSingleConfig("enabled")
+	if checkLDAPenabled == "false" {
+		common.SendTextResponse(w, "LDAP not enabled.")
+		return
+	}
+	//load the template from file and inject necessary variables
+	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) {
+	checkLDAPenabled := ldap.readSingleConfig("enabled")
+	if checkLDAPenabled == "false" {
+		common.SendTextResponse(w, "LDAP not enabled.")
+		return
+	}
+	//get the parameter from the 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
+	}
+	//init the web interface
+	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) {
+	checkLDAPenabled := ldap.readSingleConfig("enabled")
+	if checkLDAPenabled == "false" {
+		common.SendTextResponse(w, "LDAP not enabled.")
+		return
+	}
+	//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 user not exist then redirect to create pwd screen
+		if !ldap.ag.UserExists(username) {
+			authkey := ldap.syncdb.Store(username)
+			common.SendJSONResponse(w, "{\"redirect\":\"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) {
+	checkLDAPenabled := ldap.readSingleConfig("enabled")
+	if checkLDAPenabled == "false" {
+		common.SendTextResponse(w, "LDAP not enabled.")
+		return
+	}
+	//get paramters from 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, aka key is one time use only
+	//if db data match the username, proceed
+	if isValid {
+		//if not exists
+		if !ldap.ag.UserExists(username) {
+			//get the user from ldap server
+			ldapUser, err := ldap.ldapreader.GetUser(username)
+			if err != nil {
+				common.SendErrorResponse(w, err.Error())
+				return
+			}
+			//convert the ldap usergroup to arozos usergroup
+			convertedInfo := ldap.convertGroup(ldapUser)
+			//create user account and login
+			ldap.ag.CreateUserAccount(username, password, convertedInfo.EquivGroup)
+			ldap.ag.Logger.LogAuth(r, true)
+			ldap.ag.LoginUserByRequest(w, r, username, false)
+			common.SendOK(w)
+			return
+		} else {
+			//if exist then return error
+			common.SendErrorResponse(w, "User exists, please contact the system administrator if you believe this is an error.")
+			return
+		}
+	} else {
+		common.SendErrorResponse(w, "Improper key detected")
+		log.Println(r.RemoteAddr + " attempted to use invaild key to create new user but failed")
+		return
+	}
+}
+
+//HandleCheckLDAP check if ldap is enabled
+func (ldap *ldapHandler) HandleCheckLDAP(w http.ResponseWriter, r *http.Request) {
+	enabledB := false
+	enabled := ldap.readSingleConfig("enabled")
+	if enabled == "true" {
+		enabledB = true
+	}
+
+	type returnFormat struct {
+		Enabled bool `json:"enabled"`
+	}
+	json, err := json.Marshal(returnFormat{Enabled: enabledB})
+	if err != nil {
+		common.SendErrorResponse(w, "Error occurred while marshalling JSON response")
+	}
+	common.SendJSONResponse(w, string(json))
+}

+ 7 - 6
template.go → mod/common/template.go

@@ -1,8 +1,9 @@
-package main
+package common
 
 
 import (
 import (
-	"github.com/valyala/fasttemplate"
 	"io/ioutil"
 	"io/ioutil"
+
+	"github.com/valyala/fasttemplate"
 )
 )
 
 
 /*
 /*
@@ -11,12 +12,12 @@ import (
 	This is the main system core module that perform function similar to what PHP did.
 	This is the main system core module that perform function similar to what PHP did.
 	To replace part of the content of any file, use {{paramter}} to replace it.
 	To replace part of the content of any file, use {{paramter}} to replace it.
 
 
-	
+
 */
 */
 
 
-func template_load(filename string, replacement map[string]interface{}) (string, error){
+func Templateload(filename string, replacement map[string]interface{}) (string, error) {
 	content, err := ioutil.ReadFile(filename)
 	content, err := ioutil.ReadFile(filename)
-	if (err != nil){
+	if err != nil {
 		return "", nil
 		return "", nil
 	}
 	}
 	t := fasttemplate.New(string(content), "{{", "}}")
 	t := fasttemplate.New(string(content), "{{", "}}")
@@ -24,7 +25,7 @@ func template_load(filename string, replacement map[string]interface{}) (string,
 	return string(s), nil
 	return string(s), nil
 }
 }
 
 
-func template_apply(templateString string, replacement map[string]interface{}) string{
+func TemplateApply(templateString string, replacement map[string]interface{}) string {
 	t := fasttemplate.New(templateString, "{{", "}}")
 	t := fasttemplate.New(templateString, "{{", "}}")
 	s := t.ExecuteString(replacement)
 	s := t.ExecuteString(replacement)
 	return string(s)
 	return string(s)

+ 1 - 0
startup.go

@@ -88,6 +88,7 @@ func RunStartup() {
 	mediaServer_init()
 	mediaServer_init()
 	security_init()
 	security_init()
 	backup_init()
 	backup_init()
+	ldapInit() //LDAP system init
 
 
 	//Start High Level Services that requires full arozos architectures
 	//Start High Level Services that requires full arozos architectures
 	FTPServerInit() //Start FTP Server Endpoints
 	FTPServerInit() //Start FTP Server Endpoints

+ 37 - 36
system.resetpw.go

@@ -1,11 +1,12 @@
 package main
 package main
 
 
 import (
 import (
-	"net/http"
-	"log"
 	"errors"
 	"errors"
+	"log"
+	"net/http"
 
 
 	auth "imuslab.com/arozos/mod/auth"
 	auth "imuslab.com/arozos/mod/auth"
+	"imuslab.com/arozos/mod/common"
 )
 )
 
 
 /*
 /*
@@ -14,20 +15,20 @@ import (
 	This module exists to serve the password restart page with security check
 	This module exists to serve the password restart page with security check
 */
 */
 
 
-func system_resetpw_init(){
-	http.HandleFunc("/system/reset/validateResetKey", system_resetpw_validateResetKeyHandler);
-	http.HandleFunc("/system/reset/confirmPasswordReset", system_resetpw_confirmReset);
+func system_resetpw_init() {
+	http.HandleFunc("/system/reset/validateResetKey", system_resetpw_validateResetKeyHandler)
+	http.HandleFunc("/system/reset/confirmPasswordReset", system_resetpw_confirmReset)
 }
 }
 
 
 //Validate if the ysername and rkey is valid
 //Validate if the ysername and rkey is valid
-func system_resetpw_validateResetKeyHandler(w http.ResponseWriter, r *http.Request){
+func system_resetpw_validateResetKeyHandler(w http.ResponseWriter, r *http.Request) {
 	username, err := mv(r, "username", true)
 	username, err := mv(r, "username", true)
-	if err != nil{
+	if err != nil {
 		sendErrorResponse(w, "Invalid username or key")
 		sendErrorResponse(w, "Invalid username or key")
 		return
 		return
 	}
 	}
 	rkey, err := mv(r, "rkey", true)
 	rkey, err := mv(r, "rkey", true)
-	if err != nil{
+	if err != nil {
 		sendErrorResponse(w, "Invalid username or key")
 		sendErrorResponse(w, "Invalid username or key")
 		return
 		return
 	}
 	}
@@ -39,7 +40,7 @@ func system_resetpw_validateResetKeyHandler(w http.ResponseWriter, r *http.Reque
 
 
 	//Check if the pair is valid
 	//Check if the pair is valid
 	err = system_resetpw_validateResetKey(username, rkey)
 	err = system_resetpw_validateResetKey(username, rkey)
-	if err != nil{
+	if err != nil {
 		sendErrorResponse(w, err.Error())
 		sendErrorResponse(w, err.Error())
 		return
 		return
 	}
 	}
@@ -48,68 +49,68 @@ func system_resetpw_validateResetKeyHandler(w http.ResponseWriter, r *http.Reque
 
 
 }
 }
 
 
-func system_resetpw_confirmReset(w http.ResponseWriter, r *http.Request){
+func system_resetpw_confirmReset(w http.ResponseWriter, r *http.Request) {
 	username, _ := mv(r, "username", true)
 	username, _ := mv(r, "username", true)
 	rkey, _ := mv(r, "rkey", true)
 	rkey, _ := mv(r, "rkey", true)
 	newpw, _ := mv(r, "pw", true)
 	newpw, _ := mv(r, "pw", true)
-	if (username == "" || rkey == "" || newpw == ""){
+	if username == "" || rkey == "" || newpw == "" {
 		sendErrorResponse(w, "Internal Server Error")
 		sendErrorResponse(w, "Internal Server Error")
 		return
 		return
 	}
 	}
 
 
 	//Check user exists
 	//Check user exists
-	if !authAgent.UserExists(username){
+	if !authAgent.UserExists(username) {
 		sendErrorResponse(w, "Username not exists")
 		sendErrorResponse(w, "Username not exists")
 		return
 		return
 	}
 	}
 
 
 	//Validate rkey
 	//Validate rkey
 	err := system_resetpw_validateResetKey(username, rkey)
 	err := system_resetpw_validateResetKey(username, rkey)
-	if err != nil{
+	if err != nil {
 		sendErrorResponse(w, err.Error())
 		sendErrorResponse(w, err.Error())
 		return
 		return
 	}
 	}
 
 
 	//OK to procced
 	//OK to procced
 	newHashedPassword := auth.Hash(newpw)
 	newHashedPassword := auth.Hash(newpw)
-	err = sysdb.Write("auth", "passhash/" + username, newHashedPassword)
-	if err != nil{
+	err = sysdb.Write("auth", "passhash/"+username, newHashedPassword)
+	if err != nil {
 		sendErrorResponse(w, err.Error())
 		sendErrorResponse(w, err.Error())
 		return
 		return
 	}
 	}
 
 
-	sendOK(w);
+	sendOK(w)
 
 
 }
 }
 
 
-func system_resetpw_validateResetKey(username string, key string) error{
+func system_resetpw_validateResetKey(username string, key string) error {
 	//Get current password from db
 	//Get current password from db
 	passwordInDB := ""
 	passwordInDB := ""
-	err := sysdb.Read("auth", "passhash/" + username, &passwordInDB)
-	if err != nil{
+	err := sysdb.Read("auth", "passhash/"+username, &passwordInDB)
+	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	//Get hashed user key
 	//Get hashed user key
 	hashedKey := auth.Hash(key)
 	hashedKey := auth.Hash(key)
-	if (passwordInDB != hashedKey){
+	if passwordInDB != hashedKey {
 		return errors.New("Invalid Password Reset Key")
 		return errors.New("Invalid Password Reset Key")
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func system_resetpw_handlePasswordReset(w http.ResponseWriter, r *http.Request){
+func system_resetpw_handlePasswordReset(w http.ResponseWriter, r *http.Request) {
 	//Check if the user click on this link with reset password key string. If not, ask the user to input one
 	//Check if the user click on this link with reset password key string. If not, ask the user to input one
 	acc, err := mv(r, "acc", false)
 	acc, err := mv(r, "acc", false)
 	if err != nil || acc == "" {
 	if err != nil || acc == "" {
-		system_resetpw_serveIdEnterInterface(w,r);
+		system_resetpw_serveIdEnterInterface(w, r)
 		return
 		return
 	}
 	}
 
 
 	resetkey, err := mv(r, "rkey", false)
 	resetkey, err := mv(r, "rkey", false)
 	if err != nil || resetkey == "" {
 	if err != nil || resetkey == "" {
-		system_resetpw_serveIdEnterInterface(w,r);
+		system_resetpw_serveIdEnterInterface(w, r)
 		return
 		return
 	}
 	}
 
 
@@ -122,28 +123,28 @@ func system_resetpw_handlePasswordReset(w http.ResponseWriter, r *http.Request){
 
 
 	//OK. Create the New Password Entering UI
 	//OK. Create the New Password Entering UI
 	imageBase64, _ := LoadImageAsBase64("./web/" + iconVendor)
 	imageBase64, _ := LoadImageAsBase64("./web/" + iconVendor)
-	template, err := template_load("system/reset/resetPasswordTemplate.html",map[string]interface{}{
+	template, err := common.Templateload("system/reset/resetPasswordTemplate.html", map[string]interface{}{
 		"vendor_logo": imageBase64,
 		"vendor_logo": imageBase64,
-		"host_name": *host_name,
-		"username": acc,
-		"rkey": resetkey,
-	});
-	if err != nil{
-		log.Fatal(err);
+		"host_name":   *host_name,
+		"username":    acc,
+		"rkey":        resetkey,
+	})
+	if err != nil {
+		log.Fatal(err)
 	}
 	}
 	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
 	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
 	w.Write([]byte(template))
 	w.Write([]byte(template))
 }
 }
 
 
-func system_resetpw_serveIdEnterInterface(w http.ResponseWriter, r *http.Request){
+func system_resetpw_serveIdEnterInterface(w http.ResponseWriter, r *http.Request) {
 	//Reset Key or Username not found, Serve entering interface
 	//Reset Key or Username not found, Serve entering interface
 	imageBase64, _ := LoadImageAsBase64("./web/" + iconVendor)
 	imageBase64, _ := LoadImageAsBase64("./web/" + iconVendor)
-	template, err := template_load("system/reset/resetCodeTemplate.html",map[string]interface{}{
+	template, err := common.Templateload("system/reset/resetCodeTemplate.html", map[string]interface{}{
 		"vendor_logo": imageBase64,
 		"vendor_logo": imageBase64,
-		"host_name": *host_name,
-	});
-	if err != nil{
-		log.Fatal(err);
+		"host_name":   *host_name,
+	})
+	if err != nil {
+		log.Fatal(err)
 	}
 	}
 	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
 	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
 	w.Write([]byte(template))
 	w.Write([]byte(template))

+ 99 - 0
system/ldap/newPasswordTemplate.html

@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>New Password</title>
+    <link rel="stylesheet" href="../../../script/semantic/semantic.css">
+    <link rel="stylesheet" href="../../../script/ao.css">
+    <script type="application/javascript" src="../../../script/jquery.min.js"></script>
+    <script type="application/javascript" src="../../../script/semantic/semantic.js"></script>
+    <style>
+
+    </style>
+</head>
+
+<body>
+    <br><br><br>
+    <div class="ui container" align="center">
+        <div class="ui basic segment" style="max-width:400px;" align="left">
+            <div class="imageRight" align="center">
+                <img class="ui small image" src="data:image/png;base64, {{vendor_logo}}"></img>
+            </div>
+            <div class="ui divider"></div>
+            <div class="ui text container">
+                <p>Hi {{display_name}}, Please set your local password.</p>
+            </div>
+            <div class="ui divider"></div>
+            <form class="ui form" onsubmit="handleFormSubmit(event, this);">
+                <div class="ui divider"></div>
+                <div class="field">
+                    <label>New Password</label>
+                    <input id="npw" type="password" name="newpw" placeholder="New Password">
+                </div>
+                <div class="field">
+                    <label>Confirm New Password</label>
+                    <input id="cpw" type="password" name="confirmnewpw" placeholder="Confirm New Password">
+                </div>
+                <button id="submitbtn" class="ui green button" type="submit">Submit</button>
+            </form>
+            <div id="errmsg" class="ui red inverted segment" style="display:none;">
+                <i class="remove icon"></i> <span id="errtext">Internal Server Error</span>
+            </div>
+            <br>
+            <p>Back to <a href="../../../ldapLogin.system">Login</a></p>
+        </div>
+    </div>
+
+    <script>
+        var username = "{{username}}";
+        var key = "{{key}}";
+
+        function handleFormSubmit(evt, obj) {
+            evt.preventDefault();
+            var newpw = obj.newpw.value;
+            var cpw = obj.confirmnewpw.value;
+            $("#npw").parent().removeClass("error");
+            $("#cpw").parent().removeClass("error");
+
+            if (newpw != cpw) {
+                showErrorMessage("Confirm password does not match.")
+                $("#cpw").parent().addClass("error");
+                return
+            }
+
+            if (newpw == "" || cpw == "") {
+                showErrorMessage("Password cannot be empty")
+                $("#npw").parent().addClass("error");
+            }
+
+            //Should be OK now. Submit the form for reset password
+            $.ajax({
+                url: "../../../system/auth/ldap/setPassword",
+                method: "POST",
+                data: {
+                    username: username,
+                    password: newpw,
+                    authkey: key,
+                },
+                success: function(data) {
+                    if (data.error !== undefined) {
+                        showErrorMessage(data.error);
+                    } else {
+                        //OK
+                        window.location.href = "../../../";
+                    }
+                }
+            })
+
+        }
+
+        function showErrorMessage(msg) {
+            $("#errtext").text(msg);
+            $("#errmsg").stop().finish().slideDown("fast");
+        }
+    </script>
+</body>
+
+</html>

+ 215 - 0
web/SystemAO/advance/ldap.html

@@ -0,0 +1,215 @@
+<html>
+
+<head>
+    <title>LDAP Login</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+    <link rel="stylesheet" href="../../script/semantic/semantic.css">
+    <script type="application/javascript" src="../../script/jquery.min.js"></script>
+    <script type="application/javascript" src="../../script/clipboard.min.js"></script>
+    <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+    <style>
+        /* Tooltip container */
+        
+        .tooltip {
+            position: relative;
+            display: inline-block;
+            border-bottom: 1px dotted black;
+            /* If you want dots under the hoverable text */
+        }
+        /* Tooltip text */
+        
+        .tooltip .tooltiptext {
+            visibility: hidden;
+            width: 120px;
+            background-color: #555;
+            color: #fff;
+            text-align: center;
+            padding: 5px 0;
+            border-radius: 6px;
+            /* Position the tooltip text */
+            position: absolute;
+            z-index: 1;
+            bottom: 125%;
+            left: 50%;
+            margin-left: -60px;
+            /* Fade in tooltip */
+            opacity: 0;
+            transition: opacity 0.3s;
+        }
+        /* Tooltip arrow */
+        
+        .tooltip .tooltiptext::after {
+            content: "";
+            position: absolute;
+            top: 100%;
+            left: 50%;
+            margin-left: -5px;
+            border-width: 5px;
+            border-style: solid;
+            border-color: #555 transparent transparent transparent;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="ui container">
+        <div class="ui basic segment">
+            <div class="ui header">
+                <i class="key icon"></i>
+                <div class="content">
+                    LDAP Access
+                    <div class="sub header">Allow external account to access ArozOS with LDAP</div>
+                </div>
+            </div>
+        </div>
+        <div class="ui divider"></div>
+        <div class="ui green inverted segment" style="display:none;" id="updateSet">
+            <h5 class="ui header">
+                <i class="checkmark icon"></i>
+                <div class="content">
+                    Settings Updated
+                </div>
+            </h5>
+        </div>
+        <div class="ui form">
+            <div class="field">
+                <div class="ui toggle checkbox">
+                    <input type="checkbox" id="enable" name="public">
+                    <label>Enable LDAP</label>
+                </div>
+            </div>
+            <div class="field">
+                <label>Bind Username</label>
+                <div class="ui fluid input">
+                    <input type="text" id="bind_username" placeholder="root">
+                </div>
+            </div>
+            <div class="field">
+                <label>Bind Password</label>
+                <div class="ui fluid input">
+                    <input type="password" id="bind_password" placeholder="p@ssw0rd">
+                </div>
+            </div>
+            <div class="field">
+                <label>FQDN</label>
+                <div class="ui fluid input">
+                    <input type="text" id="fqdn" placeholder="10.0.0.1">
+                </div>
+            </div>
+            <div class="field">
+                <label>Base DN</label>
+                <div class="ui fluid input">
+                    <input type="text" id="base_dn" placeholder="cn=users,dc=ldap">
+                </div>
+            </div>
+            <button id="ntb" onclick="update();" class="ui green button" type="submit">Update</button>
+            <button id="test_btn" onclick="test();" class="ui button" type="submit">Test Connection</button>
+        </div>
+        <div class="ui divider"></div>
+        <div id="testConnection" style="display: none">
+            <table class="ui celled table">
+                <thead>
+                    <tr>
+                        <th>Username</th>
+                        <th>Group belongs to</th>
+                        <th>Equivalence user group in arozos</th>
+                    </tr>
+                </thead>
+                <tbody id="information">
+                </tbody>
+            </table>
+            <button id="sync_btn" onclick="syncorize();" class="ui button" type="submit">Syncorize User</button>
+        </div>
+        <br><br>
+    </div>
+
+
+    <script>
+        $(document).ready(function() {
+            read();
+        });
+
+        function read() {
+            $.getJSON("../../system/auth/ldap/config/read", function(data) {
+                if (data.enabled) {
+                    $("#enable").parent().checkbox("check")
+                }
+                if (data.autoredirect) {
+                    $("#autoredirect").parent().checkbox("check")
+                }
+                $("#bind_username").val(data.bind_username);
+                $("#bind_password").val(data.bind_password);
+                $("#fqdn").val(data.fqdn);
+                $("#base_dn").val(data.base_dn);
+            });
+        }
+
+        function update() {
+            $.post("../../system/auth/ldap/config/write", {
+                    enabled: $("#enable").parent().checkbox("is checked"),
+                    bind_username: $("#bind_username").val(),
+                    bind_password: $("#bind_password").val(),
+                    fqdn: $("#fqdn").val(),
+                    base_dn: $("#base_dn").val(),
+                })
+                .done(function(data) {
+                    if (data.error != undefined) {
+                        alert(data.error);
+                    } else {
+                        //OK!
+                        $("#updateSet").stop().finish().slideDown("fast").delay(3000).slideUp('fast');
+                    }
+                });
+        }
+
+        function test() {
+            $("#test_btn").text("Testing...");
+            $.get("../../system/auth/ldap/config/testConnection")
+                .done(function(data) {
+                    if (data.error != undefined) {
+                        if (data.error != "") {
+                            alert(data.error);
+                        }
+                    }
+                    if (data.userinfo == null) {
+                        alert("No entries was found");
+                    }
+                    //OK!
+                    $("#information").html("");
+                    $(data.userinfo).each(function(index, element) {
+                        $("#information").append(`
+                            <tr>
+                                <td data-label="username">` + element.username + `</td>
+                                <td data-label="ldap_group">` + element.group + `</td>
+                                <td data-label="equiv_group">` + element.equiv_group + `</td>
+                            </tr>
+                        `);
+                    });
+                    $("#information").append(`
+                            <tr>
+                                <td data-label="length" colspan="3">Showing ` + data.length + ` of ` + data.total_length + ` entries</td>
+                            </tr>
+                    `);
+                    $("#testConnection").show("fast");
+                    $("#test_btn").text("Test connection");
+                });
+        }
+
+        function syncorize() {
+            $("#sync_btn").text("Syncorizing...");
+            $.get("../../system/auth/ldap/config/syncorizeUser")
+                .done(function(data) {
+                    if (data.error != undefined) {
+                        alert(data.error);
+                    } else {
+                        //OK!
+                        $("#updateSet").stop().finish().slideDown("fast").delay(3000).slideUp('fast');
+                    }
+                    $("#sync_btn").text("Syncorize User");
+                });
+        }
+    </script>
+</body>
+
+</html>

+ 19 - 2
web/login.system

@@ -103,6 +103,9 @@
                 <div class="oauthonly" style="display:none;">
                 <div class="oauthonly" style="display:none;">
                     <a class="ts fluid small button oauthbtn subthemecolor" href="system/auth/oauth/login">Sign In via OAuth 2.0</a><br>
                     <a class="ts fluid small button oauthbtn subthemecolor" href="system/auth/oauth/login">Sign In via OAuth 2.0</a><br>
                 </div>
                 </div>
+                <div class="ldaponly" style="display:none;">
+                    <a class="ts fluid small button oauthbtn subthemecolor" href="ldapLogin.system">Sign In via LDAP</a><br>
+                </div>
                 <br>
                 <br>
                 <div class="ts fluid input textbox">
                 <div class="ts fluid input textbox">
                     <input id="username" type="text" placeholder="Username">
                     <input id="username" type="text" placeholder="Username">
@@ -135,6 +138,7 @@
         
         
     <script>
     <script>
         var redirectionAddress = "{{redirection_addr}}";
         var redirectionAddress = "{{redirection_addr}}";
+        var loginAddress = "{{login_addr}}";
         var systemUserCount = "{{usercount}}" - 0; //Magic way to convert string to int :)
         var systemUserCount = "{{usercount}}" - 0; //Magic way to convert string to int :)
         var autoRedirectTimer;
         var autoRedirectTimer;
         var isMobile = false; //initiate as false
         var isMobile = false; //initiate as false
@@ -215,10 +219,20 @@
                         $(".ts.borderless.basic.segment").after('<div id="autoRedirectSegment" class="ts borderless basic segment"><p><i class="key icon"></i>Redirecting to organization sign-in page in 5 seconds...</p><br><a style="cursor: pointer;" onclick="stopAutoRedirect()">Cancel</a></div>');
                         $(".ts.borderless.basic.segment").after('<div id="autoRedirectSegment" class="ts borderless basic segment"><p><i class="key icon"></i>Redirecting to organization sign-in page in 5 seconds...</p><br><a style="cursor: pointer;" onclick="stopAutoRedirect()">Cancel</a></div>');
                         autoRedirectTimer = setTimeout(function(){
                         autoRedirectTimer = setTimeout(function(){
                             window.location.href = "system/auth/oauth/login?redirect=" + redirectionAddress;
                             window.location.href = "system/auth/oauth/login?redirect=" + redirectionAddress;
-                        }, 5000);
+                        }, 3000);
                     }
                     }
                 }
                 }
             });
             });
+
+            //LDAP related code, check if system is open for ext login
+            $.getJSON("system/auth/ldap/checkldap",function(data){
+                if (data.enabled == true && window.location.pathname.toLowerCase() != "/ldaplogin.system"){
+                    $(".ldaponly").show();
+                }else{
+                    $(".ldaponly").hide();
+                }
+            });
+
             if(get('redirect') != undefined){
             if(get('redirect') != undefined){
                 $(".section.signin").attr("href","system/auth/oauth/login?redirect=" + redirectionAddress);
                 $(".section.signin").attr("href","system/auth/oauth/login?redirect=" + redirectionAddress);
             }
             }
@@ -254,11 +268,14 @@
             var magic = $("#magic").val();
             var magic = $("#magic").val();
             var rmbme = document.getElementById("rmbme").checked;
             var rmbme = document.getElementById("rmbme").checked;
             $("input").addClass('disabled');
             $("input").addClass('disabled');
-            $.post("system/auth/login", {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
+            $.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
                 if (data.error !== undefined){
                 if (data.error !== undefined){
                     //Something went wrong during the login
                     //Something went wrong during the login
                     $("#errmsg").text(data.error);
                     $("#errmsg").text(data.error);
                     $("#errmsg").parent().slideDown('fast').delay(5000).slideUp('fast');
                     $("#errmsg").parent().slideDown('fast').delay(5000).slideUp('fast');
+                }else if(data.redirect !== undefined){
+                    //LDAP Related Code
+                    window.location.href = data.redirect;
                 }else{
                 }else{
                     //Login succeed
                     //Login succeed
                     if (redirectionAddress == ""){
                     if (redirectionAddress == ""){