mirror of
https://github.com/wahyd4/loginsrv.git
synced 2026-08-09 04:46:29 +10:00
Add httpupstream backend
This commit is contained in:
@@ -26,6 +26,7 @@ The following providers (login backends) are supported.
|
||||
* [Htpasswd](#htpasswd)
|
||||
* [OSIAM](#osiam)
|
||||
* [Simple](#simple) (user/password pairs by configuration)
|
||||
* [Httpupstreem](#httpupstream)
|
||||
* [Oauth2](#oauth2)
|
||||
* Github Login
|
||||
* .. Google and Facebook will come soon ..
|
||||
@@ -197,6 +198,21 @@ Example:
|
||||
loginsrv -backend 'provider=htpasswd,file=users
|
||||
```
|
||||
|
||||
### Httpupstreem
|
||||
Authentication against an upstream http server by performing a simple simple authenticated request
|
||||
|
||||
Parameters for the provider:
|
||||
| Parameter-Name | Description |
|
||||
| ------------------|--------------------------------------------------------|
|
||||
| upstream | http/https url to call |
|
||||
| skipverify | true to ignore TLS errors (optional, false by default) |
|
||||
| timeout | request timeout (optional 1m by default) |
|
||||
|
||||
Example:
|
||||
```
|
||||
loginsrv -backend 'provider=httpupstream,upstream=https://google.com,timeout=1s'
|
||||
```
|
||||
|
||||
### OSIAM
|
||||
[OSIAM](http://osiam.org/) is a secure identity management solution providing REST based services for authentication and authorization.
|
||||
It implements the multiple OAuth2 flows, as well as SCIM for managing the user data.
|
||||
|
||||
+6
-4
@@ -3,17 +3,19 @@ package caddy
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
"github.com/tarent/loginsrv/logging"
|
||||
"github.com/tarent/loginsrv/login"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
"github.com/tarent/loginsrv/logging"
|
||||
"github.com/tarent/loginsrv/login"
|
||||
|
||||
// Import all backends, packaged with the caddy plugin
|
||||
_ "github.com/tarent/loginsrv/htpasswd"
|
||||
_ "github.com/tarent/loginsrv/httpupstream"
|
||||
_ "github.com/tarent/loginsrv/oauth2"
|
||||
_ "github.com/tarent/loginsrv/osiam"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package httpupstream
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Auth is the httpupstream authenticater
|
||||
type Auth struct {
|
||||
upstream *url.URL
|
||||
skipverify bool
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewAuth creates an httpupstream authenticater
|
||||
func NewAuth(upstream *url.URL, timeout time.Duration, skipverify bool) (*Auth, error) {
|
||||
a := &Auth{
|
||||
upstream: upstream,
|
||||
skipverify: skipverify,
|
||||
timeout: timeout,
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Authenticate the user
|
||||
func (a *Auth) Authenticate(username, password string) (bool, error) {
|
||||
c := &http.Client{
|
||||
Timeout: a.timeout,
|
||||
}
|
||||
|
||||
if a.upstream.Scheme == "https" && a.skipverify {
|
||||
c.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", a.upstream.String(), nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(username, password)
|
||||
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package httpupstream
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAuth_UnknownUser(t *testing.T) {
|
||||
ts := newTestServer()
|
||||
defer ts.Close()
|
||||
u, _ := url.Parse(ts.URL)
|
||||
|
||||
auth, err := NewAuth(u, time.Second, false)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, err := auth.Authenticate("unknown", "secret")
|
||||
NoError(t, err)
|
||||
False(t, authenticated)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package httpupstream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/tarent/loginsrv/login"
|
||||
"github.com/tarent/loginsrv/model"
|
||||
)
|
||||
|
||||
// ProviderName const
|
||||
const ProviderName = "httpupstream"
|
||||
|
||||
func init() {
|
||||
login.RegisterProvider(
|
||||
&login.ProviderDescription{
|
||||
Name: ProviderName,
|
||||
HelpText: "Httpupstream login backend opts: upstream=...,skipverify=...,timeout=...",
|
||||
},
|
||||
BackendFactory)
|
||||
}
|
||||
|
||||
// BackendFactory creates a httpupstream backend
|
||||
func BackendFactory(config map[string]string) (login.Backend, error) {
|
||||
us, ue := config["upstream"]
|
||||
ts, te := config["timeout"]
|
||||
vs, ve := config["skipverify"]
|
||||
|
||||
if !ue {
|
||||
return nil, errors.New(`missing parameter "upstream" for httpupstream provider`)
|
||||
}
|
||||
|
||||
u, err := url.Parse(us)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`invalid parameter value "%s" in "upstream" httpupstream provider: %v`, us, err)
|
||||
}
|
||||
|
||||
v := false
|
||||
t := time.Minute
|
||||
|
||||
if te {
|
||||
t, err = time.ParseDuration(ts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`invalid parameter value "%s" in "timeout" httpupstream provider: %v`, ts, err)
|
||||
}
|
||||
}
|
||||
|
||||
if ve {
|
||||
v, err = strconv.ParseBool(vs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`invalid parameter value "%s" in "skipverify" httpupstream provider: %v`, ts, err)
|
||||
}
|
||||
}
|
||||
|
||||
return NewBackend(u, t, v)
|
||||
}
|
||||
|
||||
// Backend is a httpupstream based authentication backend.
|
||||
type Backend struct {
|
||||
auth *Auth
|
||||
}
|
||||
|
||||
// NewBackend creates a new Backend and verifies the parameters.
|
||||
func NewBackend(upstream *url.URL, timeout time.Duration, skipverify bool) (*Backend, error) {
|
||||
auth, err := NewAuth(upstream, timeout, skipverify)
|
||||
return &Backend{
|
||||
auth,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Authenticate the user
|
||||
func (sb *Backend) Authenticate(username, password string) (bool, model.UserInfo, error) {
|
||||
authenticated, err := sb.auth.Authenticate(username, password)
|
||||
if authenticated && err == nil {
|
||||
return authenticated, model.UserInfo{Sub: username}, err
|
||||
}
|
||||
return false, model.UserInfo{}, err
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package httpupstream
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/stretchr/testify/assert"
|
||||
"github.com/tarent/loginsrv/login"
|
||||
)
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
p, exist := login.GetProvider(ProviderName)
|
||||
True(t, exist)
|
||||
NotNil(t, p)
|
||||
|
||||
backend, err := p(map[string]string{
|
||||
"upstream": "https://google.com",
|
||||
"skipverify": "true",
|
||||
"timeout": "20s",
|
||||
})
|
||||
|
||||
NoError(t, err)
|
||||
Equal(t,
|
||||
"https://google.com",
|
||||
backend.(*Backend).auth.upstream.String())
|
||||
Equal(t,
|
||||
true,
|
||||
backend.(*Backend).auth.skipverify)
|
||||
Equal(t,
|
||||
time.Second*20,
|
||||
backend.(*Backend).auth.timeout)
|
||||
}
|
||||
|
||||
func TestSetup_Default(t *testing.T) {
|
||||
p, exist := login.GetProvider(ProviderName)
|
||||
True(t, exist)
|
||||
NotNil(t, p)
|
||||
|
||||
backend, err := p(map[string]string{
|
||||
"upstream": "https://google.com",
|
||||
})
|
||||
|
||||
NoError(t, err)
|
||||
Equal(t,
|
||||
"https://google.com",
|
||||
backend.(*Backend).auth.upstream.String())
|
||||
Equal(t,
|
||||
false,
|
||||
backend.(*Backend).auth.skipverify)
|
||||
Equal(t,
|
||||
time.Second*60,
|
||||
backend.(*Backend).auth.timeout)
|
||||
}
|
||||
|
||||
func TestSetup_Error(t *testing.T) {
|
||||
p, exist := login.GetProvider(ProviderName)
|
||||
True(t, exist)
|
||||
NotNil(t, p)
|
||||
|
||||
_, err := p(map[string]string{})
|
||||
Error(t, err)
|
||||
}
|
||||
|
||||
func TestSimpleBackend_Authenticate(t *testing.T) {
|
||||
ts := newTestServer()
|
||||
defer ts.Close()
|
||||
u, _ := url.Parse(ts.URL)
|
||||
|
||||
backend, err := NewBackend(u, time.Second, false)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, userInfo, err := backend.Authenticate("bob-bcrypt", "secret")
|
||||
True(t, authenticated)
|
||||
Equal(t, "bob-bcrypt", userInfo.Sub)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, userInfo, err = backend.Authenticate("bob-bcrypt", "fooo")
|
||||
False(t, authenticated)
|
||||
Equal(t, "", userInfo.Sub)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, userInfo, err = backend.Authenticate("", "")
|
||||
False(t, authenticated)
|
||||
Equal(t, "", userInfo.Sub)
|
||||
NoError(t, err)
|
||||
}
|
||||
|
||||
func newTestServer() *httptest.Server {
|
||||
passwordCheck := func(w http.ResponseWriter, r *http.Request) {
|
||||
u, p, k := r.BasicAuth()
|
||||
if !k {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="test"`)
|
||||
}
|
||||
|
||||
if !(u == "bob-bcrypt" && p == "secret") {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(passwordCheck))
|
||||
}
|
||||
@@ -2,17 +2,19 @@ package main
|
||||
|
||||
import (
|
||||
_ "github.com/tarent/loginsrv/htpasswd"
|
||||
_ "github.com/tarent/loginsrv/httpupstream"
|
||||
_ "github.com/tarent/loginsrv/osiam"
|
||||
|
||||
"github.com/tarent/loginsrv/login"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/tarent/loginsrv/logging"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/tarent/loginsrv/logging"
|
||||
)
|
||||
|
||||
const applicationName = "loginsrv"
|
||||
|
||||
Reference in New Issue
Block a user