Skip to content

Commit

Permalink
GOCBC-256: Use new core callbacks for authentication.
Browse files Browse the repository at this point in the history
Motivation
----------
Due to the ongoing use of the Go SDK by teams internally
within Couchbase.  There is a need for enabling developers
to utilize the internal server authentication method.

Changes
-------
Updated SDK to utilize the new authentication callback
API exposed by gocbcore to enable use of the internal
authentication service.

Change-Id: I0a9813e5abc0cc48c26318d08ec013a0d72f0435
Reviewed-on: http://review.couchbase.org/87078
Reviewed-by: Sergey Avseyev <sergey.avseyev@gmail.com>
Tested-by: Brett Lawson <brett19@gmail.com>
  • Loading branch information
brett19 committed Dec 29, 2017
1 parent 1b4912d commit 6019ad1
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 151 deletions.
186 changes: 87 additions & 99 deletions auth.go
Original file line number Diff line number Diff line change
@@ -1,137 +1,125 @@
package gocb

// Authenticator provides an interface to authenticate to each service.
type Authenticator interface {
clusterMgmt() userPassPair
clusterN1ql() []userPassPair
clusterFts() []userPassPair
bucketMemd(bucket string) userPassPair
bucketMgmt(bucket string) userPassPair
bucketViews(bucket string) userPassPair
bucketN1ql(bucket string) []userPassPair
bucketFts(bucket string) []userPassPair
}
import "gopkg.in/couchbase/gocbcore.v7"

// BucketAuthenticator provides a password for a single bucket.
type BucketAuthenticator struct {
Password string
}
// UserPassPair represents a username and password pair.
type UserPassPair gocbcore.UserPassPair

type userPassPair struct {
Username string `json:"user"`
Password string `json:"pass"`
type coreAuthWrapper struct {
auth Authenticator
bucketName string
}

// BucketAuthenticatorMap is a map of bucket name to BucketAuthenticator.
type BucketAuthenticatorMap map[string]BucketAuthenticator

// ClusterAuthenticator implements an Authenticator which uses a list of buckets and passwords.
type ClusterAuthenticator struct {
Buckets BucketAuthenticatorMap
Username string
Password string
}

func (ca ClusterAuthenticator) clusterMgmt() userPassPair {
return userPassPair{ca.Username, ca.Password}
}

func (ca ClusterAuthenticator) clusterAll() []userPassPair {
userPassList := make([]userPassPair, len(ca.Buckets))
for bucket, auth := range ca.Buckets {
userPassList = append(userPassList, userPassPair{
Username: bucket,
Password: auth.Password,
})
// Credentials returns the credentials for a particular service.
func (auth *coreAuthWrapper) Credentials(req gocbcore.AuthCredsRequest) ([]gocbcore.UserPassPair, error) {
creds, err := auth.auth.Credentials(AuthCredsRequest{
Service: ServiceType(req.Service),
Endpoint: req.Endpoint,
Bucket: auth.bucketName,
})
if err != nil {
return nil, err
}
return userPassList
}

func (ca ClusterAuthenticator) clusterN1ql() []userPassPair {
return ca.clusterAll()
coreCreds := make([]gocbcore.UserPassPair, len(creds))
for credIdx, userPass := range creds {
coreCreds[credIdx] = gocbcore.UserPassPair(userPass)
}
return coreCreds, nil
}

func (ca ClusterAuthenticator) clusterFts() []userPassPair {
return ca.clusterAll()
// AuthCredsRequest encapsulates the data for a credential request
// from the new Authenticator interface.
// UNCOMMITTED
type AuthCredsRequest struct {
Service ServiceType
Endpoint string
Bucket string
}

func (ca ClusterAuthenticator) bucketAll(bucket string) userPassPair {
if bucketAuth, ok := ca.Buckets[bucket]; ok {
return userPassPair{bucket, bucketAuth.Password}
func getSingleCredential(auth Authenticator, req AuthCredsRequest) (UserPassPair, error) {
creds, err := auth.Credentials(req)
if err != nil {
return UserPassPair{}, err
}
return userPassPair{"", ""}
}

func (ca ClusterAuthenticator) bucketMemd(bucket string) userPassPair {
return ca.bucketAll(bucket)
}
if len(creds) != 1 {
return UserPassPair{}, gocbcore.ErrInvalidCredentials
}

func (ca ClusterAuthenticator) bucketMgmt(bucket string) userPassPair {
return ca.bucketAll(bucket)
return creds[0], nil
}

func (ca ClusterAuthenticator) bucketViews(bucket string) userPassPair {
return ca.bucketAll(bucket)
// Authenticator provides an interface to authenticate to each service. Note that
// only authenticators implemented here are stable, and support for custom
// authenticators is considered volatile.
type Authenticator interface {
Credentials(req AuthCredsRequest) ([]UserPassPair, error)
}

func (ca ClusterAuthenticator) bucketN1ql(bucket string) []userPassPair {
return []userPassPair{
ca.bucketAll(bucket),
}
// BucketAuthenticator provides a password for a single bucket.
type BucketAuthenticator struct {
Password string
}

func (ca ClusterAuthenticator) bucketFts(bucket string) []userPassPair {
return []userPassPair{
ca.bucketAll(bucket),
}
}
// BucketAuthenticatorMap is a map of bucket name to BucketAuthenticator.
type BucketAuthenticatorMap map[string]BucketAuthenticator

// PasswordAuthenticator implements an Authenticator which uses an RBAC username and password.
type PasswordAuthenticator struct {
// ClusterAuthenticator implements an Authenticator which uses a list of buckets and passwords.
type ClusterAuthenticator struct {
Buckets BucketAuthenticatorMap
Username string
Password string
}

func (ra PasswordAuthenticator) rbacAll() userPassPair {
return userPassPair{ra.Username, ra.Password}
func (ca ClusterAuthenticator) clusterCreds() []UserPassPair {
var creds []UserPassPair
for bucketName, bucket := range ca.Buckets {
creds = append(creds, UserPassPair{
Username: bucketName,
Password: bucket.Password,
})
}
return creds
}

func (ra PasswordAuthenticator) clusterMgmt() userPassPair {
return ra.rbacAll()
}
// Credentials returns the credentials for a particular service.
func (ca ClusterAuthenticator) Credentials(req AuthCredsRequest) ([]UserPassPair, error) {
if req.Bucket == "" {
if req.Service == MemdService || req.Service == MgmtService ||
req.Service == CapiService {
return []UserPassPair{{
Username: ca.Username,
Password: ca.Password,
}}, nil
}

func (ra PasswordAuthenticator) clusterN1ql() []userPassPair {
return []userPassPair{
ra.rbacAll(),
return ca.clusterCreds(), nil
}
}

func (ra PasswordAuthenticator) clusterFts() []userPassPair {
return []userPassPair{
ra.rbacAll(),
if bucketAuth, ok := ca.Buckets[req.Bucket]; ok {
return []UserPassPair{{
Username: req.Bucket,
Password: bucketAuth.Password,
}}, nil
}
}

func (ra PasswordAuthenticator) bucketMemd(bucket string) userPassPair {
return ra.rbacAll()
}

func (ra PasswordAuthenticator) bucketMgmt(bucket string) userPassPair {
return ra.rbacAll()
}

func (ra PasswordAuthenticator) bucketViews(bucket string) userPassPair {
return ra.rbacAll()
return []UserPassPair{{
Username: "",
Password: "",
}}, nil
}

func (ra PasswordAuthenticator) bucketN1ql(bucket string) []userPassPair {
return []userPassPair{
ra.rbacAll(),
}
// PasswordAuthenticator implements an Authenticator which uses an RBAC username and password.
type PasswordAuthenticator struct {
Username string
Password string
}

func (ra PasswordAuthenticator) bucketFts(bucket string) []userPassPair {
return []userPassPair{
ra.rbacAll(),
}
// Credentials returns the credentials for a particular service.
func (ra PasswordAuthenticator) Credentials(req AuthCredsRequest) ([]UserPassPair, error) {
return []UserPassPair{{
Username: ra.Username,
Password: ra.Password,
}}, nil
}
11 changes: 2 additions & 9 deletions bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,9 @@ func (b *Bucket) Internal() *BucketInternal {

// Manager returns a BucketManager for performing management operations on this bucket.
func (b *Bucket) Manager(username, password string) *BucketManager {
userPass := userPassPair{username, password}
if username == "" || password == "" {
if b.cluster.auth != nil {
userPass = b.cluster.auth.bucketMgmt(b.name)
}
}

return &BucketManager{
bucket: b,
username: userPass.Username,
password: userPass.Password,
username: username,
password: password,
}
}
10 changes: 9 additions & 1 deletion bucket_viewquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,15 @@ func (b *Bucket) executeViewQuery(viewType, ddoc, viewName string, options url.V
}

if b.cluster.auth != nil {
userPass := b.cluster.auth.bucketViews(b.name)
userPass, err := getSingleCredential(b.cluster.auth, AuthCredsRequest{
Service: CapiService,
Endpoint: capiEp,
Bucket: b.name,
})
if err != nil {
return nil, err
}

req.SetBasicAuth(userPass.Username, userPass.Password)
} else {
req.SetBasicAuth(b.name, b.password)
Expand Down
51 changes: 20 additions & 31 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,26 @@ func (c *Cluster) InvalidateQueryCache() {
c.clusterLock.Unlock()
}

func (c *Cluster) makeAgentConfig(bucket, username, password string, forceMt bool) (*gocbcore.AgentConfig, error) {
func (c *Cluster) makeAgentConfig(bucket, password string, forceMt bool) (*gocbcore.AgentConfig, error) {
auth := c.auth
if auth == nil {
authMap := make(BucketAuthenticatorMap)
authMap[bucket] = BucketAuthenticator{
Password: password,
}
auth = ClusterAuthenticator{
Buckets: authMap,
}
}

config := c.agentConfig

config.BucketName = bucket
config.Username = username
config.Password = password
config.Auth = &coreAuthWrapper{
auth: auth,
bucketName: bucket,
}

if forceMt {
config.UseMutationTokens = true
Expand All @@ -189,16 +203,7 @@ func (c *Cluster) Authenticate(auth Authenticator) error {
}

func (c *Cluster) openBucket(bucket, password string, forceMt bool) (*Bucket, error) {
username := bucket
if password == "" {
if c.auth != nil {
userPass := c.auth.bucketMemd(bucket)
username = userPass.Username
password = userPass.Password
}
}

agentConfig, err := c.makeAgentConfig(bucket, username, password, forceMt)
agentConfig, err := c.makeAgentConfig(bucket, password, forceMt)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -240,13 +245,6 @@ func (c *Cluster) closeBucket(bucket *Bucket) {

// Manager returns a ClusterManager object for performing cluster management operations on this cluster.
func (c *Cluster) Manager(username, password string) *ClusterManager {
userPass := userPassPair{username, password}
if username == "" && password == "" {
if c.auth != nil {
userPass = c.auth.clusterMgmt()
}
}

var mgmtHosts []string
for _, host := range c.agentConfig.HttpAddrs {
if c.agentConfig.TlsConfig != nil {
Expand All @@ -259,8 +257,8 @@ func (c *Cluster) Manager(username, password string) *ClusterManager {
tlsConfig := c.agentConfig.TlsConfig
return &ClusterManager{
hosts: mgmtHosts,
username: userPass.Username,
password: userPass.Password,
username: username,
password: password,
httpCli: &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
Expand All @@ -281,16 +279,7 @@ func (b *StreamingBucket) IoRouter() *gocbcore.Agent {

// OpenStreamingBucket opens a new connection to the specified bucket for the purpose of streaming data.
func (c *Cluster) OpenStreamingBucket(streamName, bucket, password string) (*StreamingBucket, error) {
username := bucket
if password == "" {
if c.auth != nil {
userPass := c.auth.bucketMemd(bucket)
username = userPass.Username
password = userPass.Password
}
}

agentConfig, err := c.makeAgentConfig(bucket, username, password, false)
agentConfig, err := c.makeAgentConfig(bucket, password, false)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 6019ad1

Please sign in to comment.