Merge branch 'pass-validation'

This commit is contained in:
Nick Penkov
2018-09-21 10:09:46 +02:00
8 changed files with 126 additions and 9 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/ .glide/
vendor/
*.lock

29
Gopkg.toml Normal file
View File

@ -0,0 +1,29 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "gopkg.in/ldap.v2"
version = "2.5.1"
[[constraint]]
name = "github.com/dchest/captcha"
branch = "master"

View File

@ -3,7 +3,10 @@ VER=1.2
.PHONY: all build push .PHONY: all build push
all: build docker push clean all: init build docker push clean
init:
dep ensure
build: build:
GOOS=linux go build -o ldap-pass-webui main.go GOOS=linux go build -o ldap-pass-webui main.go

View File

@ -4,7 +4,29 @@ WebUI Client capable of connecting to backend LDAP server and changing the users
![Screenshot](screenshots/index.png) ![Screenshot](screenshots/index.png)
## Running in docker container The configuration is made with environment variables:
|Env variable|Default value|Description|
|------------|-------------|-----------|
|LPW_TITLE|Change your global password for example.org|Title that will appear on the page|
|LPW_HOST||LDAP Host to connect to|
|LPW_PORT|636|LDAP Port (389|636 are default LDAP/LDAPS)|
|LPW_ENCRYPTED|true|Use enrypted communication|
|LPW_START_TLS|false|Start TLS communication|
|LPW_SSL_SKIP_VERIFY|true|Skip TLS CA verification|
|LPW_USER_DN|uid=%s,ou=people,dc=example,dc=org|Filter expression to search the user for Binding|
|LPW_USER_BASE|ou=people,dc=example,dc=org|Base to use when doing the binding|
## Running
```sh
dep ensure
LPW_HOST=ldap_host_ip go run main.go
```
Browse [http://localhost:8080/](http://localhost:8080/)
### Running in docker container
```sh ```sh
docker run -d -p 8080:8080 --name ldap-passwd-webui \ docker run -d -p 8080:8080 --name ldap-passwd-webui \
@ -23,6 +45,11 @@ docker run -d -p 8080:8080 --name ldap-passwd-webui \
## Building and tagging ## Building and tagging
Get [Godep](https://github.com/golang/dep)
```sh
go get -u github.com/golang/dep/cmd/dep
```
```sh ```sh
make make
``` ```
@ -31,3 +58,4 @@ make
* [Web UI for changing LDAP password - python](https://github.com/jirutka/ldap-passwd-webui) * [Web UI for changing LDAP password - python](https://github.com/jirutka/ldap-passwd-webui)
* [Gitea](https://github.com/go-gitea/gitea) * [Gitea](https://github.com/go-gitea/gitea)
* [dchest/captcha](https://github.com/dchest/captcha)

View File

@ -8,6 +8,8 @@ import (
"html/template" "html/template"
"github.com/dchest/captcha"
"regexp" "regexp"
"net/http" "net/http"
@ -19,14 +21,17 @@ type route struct {
handler http.Handler handler http.Handler
} }
// RegexpHandler is used for http handler to bind using regular expressions
type RegexpHandler struct { type RegexpHandler struct {
routes []*route routes []*route
} }
// Handler binds http handler on RegexpHandler
func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) { func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) {
h.routes = append(h.routes, &route{pattern, verb, handler}) h.routes = append(h.routes, &route{pattern, verb, handler})
} }
// HandleFunc binds http handler function on RegexpHandler
func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) { func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) {
re := regexp.MustCompile(r) re := regexp.MustCompile(r)
h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)}) h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)})
@ -48,6 +53,7 @@ type pageData struct {
PatternInfo string PatternInfo string
Username string Username string
Alerts map[string]string Alerts map[string]string
CaptchaId string
} }
// ServeAssets : Serves the static assets // ServeAssets : Serves the static assets
@ -57,7 +63,7 @@ func ServeAssets(w http.ResponseWriter, req *http.Request) {
// ServeIndex : Serves index page on GET request // ServeIndex : Serves index page on GET request
func ServeIndex(w http.ResponseWriter, req *http.Request) { func ServeIndex(w http.ResponseWriter, req *http.Request) {
p := &pageData{Title: getTitle(), Pattern: getPattern(), PatternInfo: getPatternInfo()} p := &pageData{Title: getTitle(), CaptchaId: captcha.New(), Pattern: getPattern(), PatternInfo: getPatternInfo()}
t, e := template.ParseFiles(path.Join("templates", "index.html")) t, e := template.ParseFiles(path.Join("templates", "index.html"))
if e != nil { if e != nil {
log.Printf("Error parsing file %v\n", e) log.Printf("Error parsing file %v\n", e)
@ -74,6 +80,8 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
oldPassword := req.Form["old-password"] oldPassword := req.Form["old-password"]
newPassword := req.Form["new-password"] newPassword := req.Form["new-password"]
confirmPassword := req.Form["confirm-password"] confirmPassword := req.Form["confirm-password"]
captchaID := req.Form["captchaId"]
captchaSolution := req.Form["captchaSolution"]
alerts := map[string]string{} alerts := map[string]string{}
@ -100,6 +108,12 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
alerts["error"] = alerts["error"] + fmt.Sprintf("%s", getPatternInfo()) alerts["error"] = alerts["error"] + fmt.Sprintf("%s", getPatternInfo())
} }
if len(captchaID) < 1 || captchaID[0] == "" ||
len(captchaSolution) < 1 || captchaSolution[0] == "" ||
!captcha.VerifyString(captchaID[0], captchaSolution[0]) {
alerts["error"] = "Wrong captcha."
}
if len(alerts) == 0 { if len(alerts) == 0 {
client := NewLDAPClient() client := NewLDAPClient()
if err := client.ModifyPassword(un, oldPassword[0], newPassword[0]); err != nil { if err := client.ModifyPassword(un, oldPassword[0], newPassword[0]); err != nil {
@ -109,7 +123,7 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
} }
} }
p := &pageData{Title: getTitle(), Alerts: alerts, Username: un} p := &pageData{Title: getTitle(), Alerts: alerts, Username: un, CaptchaId: captcha.New()}
t, e := template.ParseFiles(path.Join("templates", "index.html")) t, e := template.ParseFiles(path.Join("templates", "index.html"))
if e != nil { if e != nil {

View File

@ -2,8 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"github.com/npenkov/ldap-passwd-webui/app"
"net/http" "net/http"
"github.com/dchest/captcha"
"github.com/npenkov/ldap-passwd-webui/app"
) )
func main() { func main() {
@ -12,7 +14,8 @@ func main() {
reHandler.HandleFunc(".*.[js|css|png|eof|svg|ttf|woff]", "GET", app.ServeAssets) reHandler.HandleFunc(".*.[js|css|png|eof|svg|ttf|woff]", "GET", app.ServeAssets)
reHandler.HandleFunc("/", "GET", app.ServeIndex) reHandler.HandleFunc("/", "GET", app.ServeIndex)
reHandler.HandleFunc("/", "POST", app.ChangePassword) reHandler.HandleFunc("/", "POST", app.ChangePassword)
http.Handle("/captcha/", captcha.Server(captcha.StdWidth, captcha.StdHeight))
http.Handle("/", reHandler)
fmt.Println("Starting server on port 8080") fmt.Println("Starting server on port 8080")
http.ListenAndServe(":8080", reHandler) http.ListenAndServe(":8080", nil)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -12,6 +12,36 @@
</head> </head>
<body> <body>
<script>
function setSrcQuery(e, q) {
var src = e.src;
var p = src.indexOf('?');
if (p >= 0) {
src = src.substr(0, p);
}
e.src = src + "?" + q
}
function playAudio() {
var le = document.getElementById("lang");
var lang = le.options[le.selectedIndex].value;
var e = document.getElementById('audio')
setSrcQuery(e, "lang=" + lang)
e.style.display = 'block';
e.autoplay = 'true';
return false;
}
function changeLang() {
var e = document.getElementById('audio')
if (e.style.display == 'block') {
playAudio();
}
}
function reload() {
setSrcQuery(document.getElementById('image'), "reload=" + (new Date()).getTime());
setSrcQuery(document.getElementById('audio'), (new Date()).getTime());
return false;
}
</script>
<main> <main>
<h1>{{.Title}}</h1> <h1>{{.Title}}</h1>
@ -30,6 +60,14 @@
<input id="confirm-password" name="confirm-password" type="password" <input id="confirm-password" name="confirm-password" type="password"
pattern="{{.Pattern}}" x-moz-errormessage="{{.PatternInfo}}" required> pattern="{{.Pattern}}" x-moz-errormessage="{{.PatternInfo}}" required>
<p>{{.PatternInfo}}</p> <p>{{.PatternInfo}}</p>
<p>Type the numbers you see in the picture below:</p>
<p>
<img id=image src="/captcha/{{.CaptchaId}}.png" alt="Captcha image">
</p>
<a href="#" onclick="reload()">Reload</a>
<input type=hidden name=captchaId value="{{.CaptchaId}}">
<br>
<input id="captchaSolution" name="captchaSolution" type="text" required>
<button type="submit">Update password</button> <button type="submit">Update password</button>
</form> </form>