blob: 2ea1bfc3000404208d88f209c7cc427491c5f3f4 [file] [log] [blame]
Don Newton379ae252019-04-01 12:17:06 -04001// Copyright (C) MongoDB, Inc. 2017-present.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
7package auth
8
9import (
10 "context"
11 "fmt"
12
13 "github.com/mongodb/mongo-go-driver/x/network/address"
14 "github.com/mongodb/mongo-go-driver/x/network/command"
15 "github.com/mongodb/mongo-go-driver/x/network/connection"
16 "github.com/mongodb/mongo-go-driver/x/network/description"
17 "github.com/mongodb/mongo-go-driver/x/network/wiremessage"
18)
19
20// AuthenticatorFactory constructs an authenticator.
21type AuthenticatorFactory func(cred *Cred) (Authenticator, error)
22
23var authFactories = make(map[string]AuthenticatorFactory)
24
25func init() {
26 RegisterAuthenticatorFactory("", newDefaultAuthenticator)
27 RegisterAuthenticatorFactory(SCRAMSHA1, newScramSHA1Authenticator)
28 RegisterAuthenticatorFactory(SCRAMSHA256, newScramSHA256Authenticator)
29 RegisterAuthenticatorFactory(MONGODBCR, newMongoDBCRAuthenticator)
30 RegisterAuthenticatorFactory(PLAIN, newPlainAuthenticator)
31 RegisterAuthenticatorFactory(GSSAPI, newGSSAPIAuthenticator)
32 RegisterAuthenticatorFactory(MongoDBX509, newMongoDBX509Authenticator)
33}
34
35// CreateAuthenticator creates an authenticator.
36func CreateAuthenticator(name string, cred *Cred) (Authenticator, error) {
37 if f, ok := authFactories[name]; ok {
38 return f(cred)
39 }
40
41 return nil, newAuthError(fmt.Sprintf("unknown authenticator: %s", name), nil)
42}
43
44// RegisterAuthenticatorFactory registers the authenticator factory.
45func RegisterAuthenticatorFactory(name string, factory AuthenticatorFactory) {
46 authFactories[name] = factory
47}
48
49// // Opener returns a connection opener that will open and authenticate the connection.
50// func Opener(opener conn.Opener, authenticator Authenticator) conn.Opener {
51// return func(ctx context.Context, addr model.Addr, opts ...conn.Option) (conn.Connection, error) {
52// return NewConnection(ctx, authenticator, opener, addr, opts...)
53// }
54// }
55//
56// // NewConnection opens a connection and authenticates it.
57// func NewConnection(ctx context.Context, authenticator Authenticator, opener conn.Opener, addr model.Addr, opts ...conn.Option) (conn.Connection, error) {
58// conn, err := opener(ctx, addr, opts...)
59// if err != nil {
60// if conn != nil {
61// // Ignore any error that occurs since we're already returning a different one.
62// _ = conn.Close()
63// }
64// return nil, err
65// }
66//
67// err = authenticator.Auth(ctx, conn)
68// if err != nil {
69// // Ignore any error that occurs since we're already returning a different one.
70// _ = conn.Close()
71// return nil, err
72// }
73//
74// return conn, nil
75// }
76
77// Configurer creates a connection configurer for the given authenticator.
78//
79// TODO(skriptble): Fully implement this once this package is moved over to the new connection type.
80// func Configurer(configurer connection.Configurer, authenticator Authenticator) connection.Configurer {
81// return connection.ConfigurerFunc(func(ctx context.Context, conn connection.Connection) (connection.Connection, error) {
82// err := authenticator.Auth(ctx, conn)
83// if err != nil {
84// conn.Close()
85// return nil, err
86// }
87// if configurer == nil {
88// return conn, nil
89// }
90// return configurer.Configure(ctx, conn)
91// })
92// }
93
94// HandshakeOptions packages options that can be passed to the Handshaker()
95// function. DBUser is optional but must be of the form <dbname.username>;
96// if non-empty, then the connection will do SASL mechanism negotiation.
97type HandshakeOptions struct {
98 AppName string
99 Authenticator Authenticator
100 Compressors []string
101 DBUser string
102}
103
104// Handshaker creates a connection handshaker for the given authenticator.
105func Handshaker(h connection.Handshaker, options *HandshakeOptions) connection.Handshaker {
106 return connection.HandshakerFunc(func(ctx context.Context, addr address.Address, rw wiremessage.ReadWriter) (description.Server, error) {
107 desc, err := (&command.Handshake{
108 Client: command.ClientDoc(options.AppName),
109 Compressors: options.Compressors,
110 SaslSupportedMechs: options.DBUser,
111 }).Handshake(ctx, addr, rw)
112
113 if err != nil {
114 return description.Server{}, newAuthError("handshake failure", err)
115 }
116
117 err = options.Authenticator.Auth(ctx, desc, rw)
118 if err != nil {
119 return description.Server{}, newAuthError("auth error", err)
120 }
121 if h == nil {
122 return desc, nil
123 }
124 return h.Handshake(ctx, addr, rw)
125 })
126}
127
128// Authenticator handles authenticating a connection.
129type Authenticator interface {
130 // Auth authenticates the connection.
131 Auth(context.Context, description.Server, wiremessage.ReadWriter) error
132}
133
134func newAuthError(msg string, inner error) error {
135 return &Error{
136 message: msg,
137 inner: inner,
138 }
139}
140
141func newError(err error, mech string) error {
142 return &Error{
143 message: fmt.Sprintf("unable to authenticate using mechanism \"%s\"", mech),
144 inner: err,
145 }
146}
147
148// Error is an error that occurred during authentication.
149type Error struct {
150 message string
151 inner error
152}
153
154func (e *Error) Error() string {
155 if e.inner == nil {
156 return e.message
157 }
158 return fmt.Sprintf("%s: %s", e.message, e.inner)
159}
160
161// Inner returns the wrapped error.
162func (e *Error) Inner() error {
163 return e.inner
164}
165
166// Message returns the message.
167func (e *Error) Message() string {
168 return e.message
169}