You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
7.8 KiB
279 lines
7.8 KiB
//
|
|
// DISCLAIMER
|
|
//
|
|
// Copyright 2017 ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
//
|
|
// Author Ewout Prangsma
|
|
//
|
|
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
driver "github.com/arangodb/go-driver"
|
|
)
|
|
|
|
// ErrAuthenticationNotChanged is returned when authentication is not changed.
|
|
var ErrAuthenticationNotChanged = errors.New("authentication not changed")
|
|
|
|
// Authentication implements a kind of authentication.
|
|
type httpAuthentication interface {
|
|
// Prepare is called before the first request of the given connection is made.
|
|
Prepare(ctx context.Context, conn driver.Connection) error
|
|
|
|
// Configure is called for every request made on a connection.
|
|
Configure(req driver.Request) error
|
|
}
|
|
|
|
// IsAuthenticationTheSame checks whether two authentications are the same.
|
|
func IsAuthenticationTheSame(auth1, auth2 driver.Authentication) bool {
|
|
|
|
if auth1 == nil && auth2 == nil {
|
|
return true
|
|
}
|
|
|
|
if auth1 == nil || auth2 == nil {
|
|
return false
|
|
}
|
|
|
|
if auth1.Type() != auth2.Type() {
|
|
return false
|
|
}
|
|
|
|
if auth1.Type() == driver.AuthenticationTypeRaw {
|
|
if auth1.Get("value") != auth2.Get("value") {
|
|
return false
|
|
}
|
|
} else {
|
|
if auth1.Get("username") != auth2.Get("username") ||
|
|
auth1.Get("password") != auth2.Get("password") {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// newBasicAuthentication creates an authentication implementation based on the given username & password.
|
|
func newBasicAuthentication(userName, password string) httpAuthentication {
|
|
auth := fmt.Sprintf("%s:%s", userName, password)
|
|
encoded := base64.StdEncoding.EncodeToString([]byte(auth))
|
|
return &basicAuthentication{
|
|
authorizationValue: "Basic " + encoded,
|
|
}
|
|
}
|
|
|
|
// newJWTAuthentication creates a JWT token authentication implementation based on the given username & password.
|
|
func newJWTAuthentication(userName, password string) httpAuthentication {
|
|
return &jwtAuthentication{
|
|
userName: userName,
|
|
password: password,
|
|
}
|
|
}
|
|
|
|
// newRawAuthentication creates a Raw authentication implementation based on the given value.
|
|
func newRawAuthentication(value string) httpAuthentication {
|
|
return &basicAuthentication{
|
|
authorizationValue: value,
|
|
}
|
|
}
|
|
|
|
// basicAuthentication implements HTTP Basic authentication.
|
|
type basicAuthentication struct {
|
|
authorizationValue string
|
|
}
|
|
|
|
// Prepare is called before the first request of the given connection is made.
|
|
func (a *basicAuthentication) Prepare(ctx context.Context, conn driver.Connection) error {
|
|
// No need to do anything here
|
|
return nil
|
|
}
|
|
|
|
// Configure is called for every request made on a connection.
|
|
func (a *basicAuthentication) Configure(req driver.Request) error {
|
|
req.SetHeader("Authorization", a.authorizationValue)
|
|
return nil
|
|
}
|
|
|
|
// jwtAuthentication implements JWT token authentication.
|
|
type jwtAuthentication struct {
|
|
userName string
|
|
password string
|
|
token string
|
|
}
|
|
|
|
type jwtOpenRequest struct {
|
|
UserName string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type jwtOpenResponse struct {
|
|
Token string `json:"jwt"`
|
|
MustChangePassword bool `json:"must_change_password,omitempty"`
|
|
}
|
|
|
|
// Prepare is called before the first request of the given connection is made.
|
|
func (a *jwtAuthentication) Prepare(ctx context.Context, conn driver.Connection) error {
|
|
// Prepare request
|
|
r, err := conn.NewRequest("POST", "/_open/auth")
|
|
if err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
r.SetBody(jwtOpenRequest{
|
|
UserName: a.userName,
|
|
Password: a.password,
|
|
})
|
|
|
|
// Perform request
|
|
resp, err := conn.Do(ctx, r)
|
|
if err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
if err := resp.CheckStatus(200); err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
|
|
// Parse response
|
|
var data jwtOpenResponse
|
|
if err := resp.ParseBody("", &data); err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
|
|
// Store token
|
|
a.token = data.Token
|
|
|
|
// Ok
|
|
return nil
|
|
}
|
|
|
|
// Configure is called for every request made on a connection.
|
|
func (a *jwtAuthentication) Configure(req driver.Request) error {
|
|
req.SetHeader("Authorization", "bearer "+a.token)
|
|
return nil
|
|
}
|
|
|
|
// newAuthenticatedConnection creates a Connection that applies the given connection on the given underlying connection.
|
|
func newAuthenticatedConnection(conn driver.Connection, auth httpAuthentication) (driver.Connection, error) {
|
|
if conn == nil {
|
|
return nil, driver.WithStack(driver.InvalidArgumentError{Message: "conn is nil"})
|
|
}
|
|
if auth == nil {
|
|
return nil, driver.WithStack(driver.InvalidArgumentError{Message: "auth is nil"})
|
|
}
|
|
return &authenticatedConnection{
|
|
conn: conn,
|
|
auth: auth,
|
|
}, nil
|
|
}
|
|
|
|
// authenticatedConnection implements authentication behavior for connections.
|
|
type authenticatedConnection struct {
|
|
conn driver.Connection // Un-authenticated connection
|
|
auth httpAuthentication
|
|
prepareMutex sync.Mutex
|
|
prepared int32
|
|
}
|
|
|
|
// NewRequest creates a new request with given method and path.
|
|
func (c *authenticatedConnection) NewRequest(method, path string) (driver.Request, error) {
|
|
r, err := c.conn.NewRequest(method, path)
|
|
if err != nil {
|
|
return nil, driver.WithStack(err)
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// Do performs a given request, returning its response.
|
|
func (c *authenticatedConnection) Do(ctx context.Context, req driver.Request) (driver.Response, error) {
|
|
if atomic.LoadInt32(&c.prepared) == 0 {
|
|
// Probably we're not yet prepared
|
|
if err := c.prepare(ctx); err != nil {
|
|
// Authentication failed
|
|
return nil, driver.WithStack(err)
|
|
}
|
|
}
|
|
// Configure the request for authentication.
|
|
if err := c.auth.Configure(req); err != nil {
|
|
// Failed to configure request for authentication
|
|
return nil, driver.WithStack(err)
|
|
}
|
|
// Do the authenticated request
|
|
resp, err := c.conn.Do(ctx, req)
|
|
if err != nil {
|
|
return nil, driver.WithStack(err)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// Unmarshal unmarshals the given raw object into the given result interface.
|
|
func (c *authenticatedConnection) Unmarshal(data driver.RawObject, result interface{}) error {
|
|
if err := c.conn.Unmarshal(data, result); err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Endpoints returns the endpoints used by this connection.
|
|
func (c *authenticatedConnection) Endpoints() []string {
|
|
return c.conn.Endpoints()
|
|
}
|
|
|
|
// UpdateEndpoints reconfigures the connection to use the given endpoints.
|
|
func (c *authenticatedConnection) UpdateEndpoints(endpoints []string) error {
|
|
if err := c.conn.UpdateEndpoints(endpoints); err != nil {
|
|
return driver.WithStack(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetAuthentication creates a copy of connection wrapper for given auth parameters.
|
|
func (c *authenticatedConnection) SetAuthentication(auth driver.Authentication) (driver.Connection, error) {
|
|
result, err := c.conn.SetAuthentication(auth)
|
|
if err != nil {
|
|
return nil, driver.WithStack(err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Protocols returns all protocols used by this connection.
|
|
func (c *authenticatedConnection) Protocols() driver.ProtocolSet {
|
|
return c.conn.Protocols()
|
|
}
|
|
|
|
// prepare calls Authentication.Prepare if needed.
|
|
func (c *authenticatedConnection) prepare(ctx context.Context) error {
|
|
c.prepareMutex.Lock()
|
|
defer c.prepareMutex.Unlock()
|
|
if c.prepared == 0 {
|
|
// We need to prepare first
|
|
if err := c.auth.Prepare(ctx, c.conn); err != nil {
|
|
// Authentication failed
|
|
return driver.WithStack(err)
|
|
}
|
|
// We're now prepared
|
|
atomic.StoreInt32(&c.prepared, 1)
|
|
} else {
|
|
// We're already prepared, do nothing
|
|
}
|
|
return nil
|
|
}
|
|
|