Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Companion-API oauth register/update user #7

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"errors"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/emicklei/go-restful"
jwt "github.com/dgrijalva/jwt-go"
restful "github.com/emicklei/go-restful"
)

var (
Expand All @@ -16,6 +16,7 @@ var (
// Authenticator is the interface for authn backends
type Authenticator interface {
Authenticate(user, password string, expiresAt time.Time) (claims jwt.Claims, err error)
FindUser(clientID, provider string, expiresAt time.Time) (user string, claims jwt.Claims, err error)
}

// API registering with restful
Expand All @@ -36,5 +37,6 @@ func (api *API) Register() *restful.WebService {
api.registerKeystone(ws)
api.registerK8sAuthenticator(ws)
api.registerCertificate(ws)
api.registerOauth(ws)
return ws
}
2 changes: 1 addition & 1 deletion api/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package api
import (
"time"

"github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go"

"github.com/mcluseau/autentigo/auth"
)
Expand Down
114 changes: 114 additions & 0 deletions api/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package api

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"

restful "github.com/emicklei/go-restful"
)

const bearerPrefix = "Bearer "

func (api *API) registerOauth(ws *restful.WebService) {
ws.
Route(ws.GET("/oauth/{provider}").
To(api.oauthAuthenticate).
Doc("Authenticate using oauth token").
Param(restful.HeaderParameter("Authorization", "Oauth authorization header")).
Produces("application/json").
Writes(AuthResponse{}))
}

func (api *API) oauthAuthenticate(request *restful.Request, response *restful.Response) {
defer func() {
if err := recover(); err != nil {
// unhandled error
WriteError(err.(error), response)
}
}()

authHeader := request.HeaderParameter("Authorization")
if !strings.HasPrefix(authHeader, bearerPrefix) {
response.WriteErrorString(http.StatusUnauthorized, "missing bearer prefix")
return
}

accessToken := authHeader[len(bearerPrefix):]
provider := request.PathParameter("provider")

baseURL, err := oauthClientIdentityURL(provider)
if err != nil {
response.WriteError(http.StatusBadRequest, err)
return
}

identityResponse, err := http.Get(baseURL + "?access_token=" + accessToken)
if err != nil {
response.WriteError(http.StatusUnauthorized, fmt.Errorf("failed getting client identity by oauth: %s", err.Error()))
return
}

defer identityResponse.Body.Close()
contents, err := ioutil.ReadAll(identityResponse.Body)
if err != nil {
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf("failed reading response body: %s", err.Error()))
return
}

var clientIdentity map[string]interface{}
if err := json.Unmarshal(contents, &clientIdentity); err != nil {
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf("failed unmarshalling contents: %s", err.Error()))
return
}

id := stringValue(clientIdentity, "id")
if len(id) == 0 {
id = stringValue(clientIdentity, "sub") // different between some oauth providers
if len(id) == 0 {
response.WriteErrorString(http.StatusUnprocessableEntity, "client identity given by oauth is unprocessable")
return
}
}

exp := time.Now().Add(api.TokenDuration)
user, claims, err := api.Authenticator.FindUser(id, provider, exp)
if err != nil {
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf("associated user not found: %s", err.Error()))
return
}
_, tokenString, err := api.createToken(user, claims)
if err != nil {
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf("Not possible to create valid token: %s", err.Error()))
return
}

_, err = api.checkToken(tokenString)
if err != nil {
response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf("Not valid token after creation: %s", err.Error()))
return
}

response.WriteEntity(&AuthResponse{tokenString, claims})
}

func stringValue(m map[string]interface{}, field string) string {
v, ok := m[field]
if !ok {
return ""
}
return v.(string)
}

func oauthClientIdentityURL(provider string) (value string, err error) {
urlEnv := strings.ToUpper(provider) + "_USERIDENTITYURL"
value = os.Getenv(urlEnv)
if len(value) == 0 {
err = fmt.Errorf("client identity url given by provider %s is missing, please verify autentigo configuration [%s]", provider, urlEnv)
}
return
}
57 changes: 50 additions & 7 deletions auth/etcd/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"log"
"os"
"path"
Expand All @@ -17,6 +18,10 @@ import (
"github.com/mcluseau/autentigo/auth"
)

const (
oauthprefix = "/oauth"
)

// New Authenticator with etcd backend
func New(prefix string, endpoints []string) api.Authenticator {
client, err := clientv3.New(clientv3.Config{
Expand Down Expand Up @@ -64,7 +69,29 @@ func (a *etcdAuth) Authenticate(user, password string, expiresAt time.Time) (cla
ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
defer cancel()

resp, err := a.client.Get(ctx, path.Join(a.prefix, user))
u := &User{}
if u, err = a.getUser(ctx, user); err != nil {
return
}

if u.PasswordHash != passwordHash {
err = api.ErrInvalidAuthentication
return
}

claims = auth.Claims{
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: expiresAt.Unix(),
Subject: user,
},
ExtraClaims: u.ExtraClaims,
}
return
}

func (a *etcdAuth) getUser(ctx context.Context, userID string) (user *User, err error) {
resp, err := a.client.Get(ctx, path.Join(a.prefix, userID))
if err != nil {
return
}
Expand All @@ -74,23 +101,39 @@ func (a *etcdAuth) Authenticate(user, password string, expiresAt time.Time) (cla
return
}

u := User{}
if err = json.Unmarshal(resp.Kvs[0].Value, &u); err != nil {
user = &User{}
err = json.Unmarshal(resp.Kvs[0].Value, user)
return
}

func (a *etcdAuth) FindUser(clientID, provider string, expiresAt time.Time) (userID string, claims jwt.Claims, err error) {

ctx, cancel := context.WithTimeout(context.Background(), a.timeout)
defer cancel()

var resp *clientv3.GetResponse
if resp, err = a.client.Get(ctx, path.Join(oauthprefix, a.prefix, provider, clientID)); err != nil {
return
}

if len(resp.Kvs) == 0 {
err = errors.New("unknown user")
return
}

if u.PasswordHash != passwordHash {
err = api.ErrInvalidAuthentication
userID = string(resp.Kvs[0].Value)
user := &User{}
if user, err = a.getUser(ctx, userID); err != nil {
return
}

claims = auth.Claims{
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: expiresAt.Unix(),
Subject: user,
Subject: userID,
},
ExtraClaims: u.ExtraClaims,
ExtraClaims: user.ExtraClaims,
}
return
}
10 changes: 8 additions & 2 deletions auth/ldap-bind/ldap-bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package ldapbind

import (
"crypto/tls"
"errors"
"fmt"
"log"
"net/url"
"time"

"github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go"
"github.com/mcluseau/autentigo/api"
"gopkg.in/ldap.v2"
ldap "gopkg.in/ldap.v2"
)

// New Authenticator with ldap backend
Expand Down Expand Up @@ -64,3 +65,8 @@ func (a auth) Authenticate(user, password string, expiresAt time.Time) (jwt.Clai
Subject: user,
}, nil
}

func (a auth) FindUser(clientID, provider string, expiresAt time.Time) (userID string, claims jwt.Claims, err error) {
err = errors.New("inconsistent with Ldap backend")
return
}
6 changes: 6 additions & 0 deletions auth/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"errors"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -74,3 +75,8 @@ func (sa sqlAuth) Authenticate(user, password string, expiresAt time.Time) (clai

return
}

func (sa sqlAuth) FindUser(clientID, provider string, expiresAt time.Time) (userID string, claims jwt.Claims, err error) {
err = errors.New("Not implemented yet")
return
}
8 changes: 7 additions & 1 deletion auth/stupid-auth/stupid-auth.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package stupidauth

import (
"errors"
"time"

"github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go"
"github.com/mcluseau/autentigo/api"
)

Expand All @@ -23,3 +24,8 @@ func (sa stupidAuth) Authenticate(user, password string, expiresAt time.Time) (j
Subject: user,
}, nil
}

func (sa stupidAuth) FindUser(clientID, provider string, expiresAt time.Time) (userID string, claims jwt.Claims, err error) {
err = errors.New("inconsistent with stupid auth")
return
}
6 changes: 6 additions & 0 deletions auth/users-file/users-file.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/csv"
"encoding/hex"
"errors"
"io"
"os"
"strings"
Expand Down Expand Up @@ -96,3 +97,8 @@ func (a usersFileAuth) Authenticate(user, password string, expiresAt time.Time)

return nil, api.ErrInvalidAuthentication
}

func (a usersFileAuth) FindUser(clientID, provider string, expiresAt time.Time) (userID string, claims jwt.Claims, err error) {
err = errors.New("Not implemented yet")
return
}
2 changes: 1 addition & 1 deletion cmd/ag-companion-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
companionapi "github.com/mcluseau/autentigo/pkg/companion-api/api"
"github.com/mcluseau/autentigo/pkg/companion-api/backend"
"github.com/mcluseau/autentigo/pkg/companion-api/backend/etcd"
"github.com/mcluseau/autentigo/pkg/companion-api/backend/users-file"
usersfile "github.com/mcluseau/autentigo/pkg/companion-api/backend/users-file"
"github.com/mcluseau/autentigo/pkg/rbac"
)

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/mobile v0.0.0-20190806162312-597adff16ade // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
golang.org/x/tools v0.0.0-20190813034749-528a2984e271 // indirect
google.golang.org/grpc v1.22.1 // indirect
Expand All @@ -64,3 +65,5 @@ require (
k8s.io/kube-openapi v0.0.0-20190722073852-5e22f3d471e6 // indirect
sigs.k8s.io/structured-merge-diff v0.0.0-20190724202554-0c1d754dd648 // indirect
)

go 1.13
Loading