1
1
import { AuthProvider , AuthContext } from './auth_provider' ;
2
2
import { MongoError } from '../../error' ;
3
- import { Kerberos } from '../../deps' ;
3
+ import { Kerberos , KerberosClient } from '../../deps' ;
4
4
import type { Callback } from '../../utils' ;
5
+ import type { HandshakeDocument } from '../connect' ;
6
+ import type { Document } from '../../bson' ;
7
+
8
+ const kGssapiClient = Symbol ( 'GSSAPI_CLIENT' ) ;
9
+
10
+ type MechanismProperties = {
11
+ gssapiCanonicalizeHostName ?: boolean ;
12
+ } ;
13
+
14
+ import * as dns from 'dns' ;
5
15
6
16
export class GSSAPI extends AuthProvider {
7
- auth ( authContext : AuthContext , callback : Callback ) : void {
17
+ [ kGssapiClient ] : KerberosClient ;
18
+ prepare (
19
+ handshakeDoc : HandshakeDocument ,
20
+ authContext : AuthContext ,
21
+ callback : Callback < HandshakeDocument >
22
+ ) : void {
8
23
const { host, port } = authContext . options ;
9
- const { connection , credentials } = authContext ;
24
+ const { credentials } = authContext ;
10
25
if ( ! host || ! port || ! credentials ) {
11
26
return callback (
12
27
new MongoError (
@@ -21,65 +36,154 @@ export class GSSAPI extends AuthProvider {
21
36
return callback ( Kerberos [ 'kModuleError' ] ) ;
22
37
}
23
38
24
- const username = credentials . username ;
25
- const password = credentials . password ;
26
- const mechanismProperties = credentials . mechanismProperties ;
27
- const gssapiServiceName =
39
+ const { username, password, mechanismProperties } = credentials ;
40
+ const serviceName =
28
41
mechanismProperties [ 'gssapiservicename' ] ||
29
42
mechanismProperties [ 'gssapiServiceName' ] ||
30
43
'mongodb' ;
31
44
32
- const MongoAuthProcess = Kerberos . processes . MongoAuthProcess ;
33
-
34
- const authProcess = new MongoAuthProcess ( host , port , gssapiServiceName , mechanismProperties ) ;
45
+ performGssapiCanonicalizeHostName (
46
+ host ,
47
+ mechanismProperties as MechanismProperties ,
48
+ ( err ?: Error | MongoError , host ?: string ) => {
49
+ if ( err ) return callback ( err ) ;
35
50
36
- authProcess . init ( username , password , err => {
37
- if ( err ) return callback ( err , false ) ;
38
- authProcess . transition ( '' , ( err , payload ) => {
39
- if ( err ) return callback ( err , false ) ;
51
+ const initOptions = { } ;
52
+ if ( password != null ) {
53
+ Object . assign ( initOptions , { user : username , password : password } ) ;
54
+ }
40
55
41
- const command = {
42
- saslStart : 1 ,
43
- mechanism : 'GSSAPI' ,
44
- payload,
45
- autoAuthorize : 1
46
- } ;
56
+ Kerberos . initializeClient (
57
+ `${ serviceName } ${ process . platform === 'win32' ? '/' : '@' } ${ host } ` ,
58
+ initOptions ,
59
+ ( err : string , client : KerberosClient ) : void => {
60
+ if ( err ) return callback ( new MongoError ( err ) ) ;
61
+ if ( client == null ) return callback ( ) ;
62
+ this [ kGssapiClient ] = client ;
63
+ callback ( undefined , handshakeDoc ) ;
64
+ }
65
+ ) ;
66
+ }
67
+ ) ;
68
+ }
47
69
48
- connection . command ( '$external.$cmd' , command , ( err , doc ) => {
49
- if ( err ) return callback ( err , false ) ;
70
+ auth ( authContext : AuthContext , callback : Callback ) : void {
71
+ const { connection, credentials } = authContext ;
72
+ if ( credentials == null ) return callback ( new MongoError ( 'credentials required' ) ) ;
73
+ const { username } = credentials ;
74
+ const client = this [ kGssapiClient ] ;
75
+ if ( client == null ) return callback ( new MongoError ( 'gssapi client missing' ) ) ;
76
+ function externalCommand (
77
+ command : Document ,
78
+ cb : Callback < { payload : string ; conversationId : any } >
79
+ ) {
80
+ return connection . command ( '$external.$cmd' , command , cb ) ;
81
+ }
82
+ client . step ( '' , ( err , payload ) => {
83
+ if ( err ) return callback ( err ) ;
50
84
51
- authProcess . transition ( doc . payload , ( err , payload ) => {
52
- if ( err ) return callback ( err , false ) ;
53
- const command = {
54
- saslContinue : 1 ,
55
- conversationId : doc . conversationId ,
56
- payload
57
- } ;
85
+ externalCommand ( saslStart ( payload ) , ( err , result ) => {
86
+ if ( err ) return callback ( err ) ;
87
+ if ( result == null ) return callback ( ) ;
88
+ negotiate ( client , 10 , result . payload , ( err , payload ) => {
89
+ if ( err ) return callback ( err ) ;
58
90
59
- connection . command ( '$external.$cmd' , command , ( err , doc ) => {
60
- if ( err ) return callback ( err , false ) ;
91
+ externalCommand ( saslContinue ( payload , result . conversationId ) , ( err , result ) => {
92
+ if ( err ) return callback ( err ) ;
93
+ if ( result == null ) return callback ( ) ;
94
+ finalize ( client , username , result . payload , ( err , payload ) => {
95
+ if ( err ) return callback ( err ) ;
61
96
62
- authProcess . transition ( doc . payload , ( err , payload ) => {
63
- if ( err ) return callback ( err , false ) ;
64
- const command = {
97
+ externalCommand (
98
+ {
65
99
saslContinue : 1 ,
66
- conversationId : doc . conversationId ,
100
+ conversationId : result . conversationId ,
67
101
payload
68
- } ;
102
+ } ,
103
+ ( err , result ) => {
104
+ if ( err ) return callback ( err ) ;
69
105
70
- connection . command ( '$external.$cmd' , command , ( err , response ) => {
71
- if ( err ) return callback ( err , false ) ;
72
-
73
- authProcess . transition ( null , err => {
74
- if ( err ) return callback ( err ) ;
75
- callback ( undefined , response ) ;
76
- } ) ;
77
- } ) ;
78
- } ) ;
106
+ callback ( undefined , result ) ;
107
+ }
108
+ ) ;
79
109
} ) ;
80
110
} ) ;
81
111
} ) ;
82
112
} ) ;
83
113
} ) ;
84
114
}
85
115
}
116
+ function saslStart ( payload ?: string ) : Document {
117
+ return {
118
+ saslStart : 1 ,
119
+ mechanism : 'GSSAPI' ,
120
+ payload,
121
+ autoAuthorize : 1
122
+ } ;
123
+ }
124
+
125
+ function saslContinue ( payload ?: string , conversationId ?: number ) : Document {
126
+ return {
127
+ saslContinue : 1 ,
128
+ conversationId,
129
+ payload
130
+ } ;
131
+ }
132
+
133
+ function negotiate (
134
+ client : KerberosClient ,
135
+ retries : number ,
136
+ payload : string ,
137
+ callback : Callback < string >
138
+ ) : void {
139
+ client . step ( payload , ( err , response ) => {
140
+ // Retries exhausted, raise error
141
+ if ( err && retries === 0 ) return callback ( err ) ;
142
+
143
+ // Adjust number of retries and call step again
144
+ if ( err ) return negotiate ( client , retries - 1 , payload , callback ) ;
145
+
146
+ // Return the payload
147
+ callback ( undefined , response || '' ) ;
148
+ } ) ;
149
+ }
150
+
151
+ function finalize (
152
+ client : KerberosClient ,
153
+ user : string ,
154
+ payload : string ,
155
+ callback : Callback < string >
156
+ ) : void {
157
+ // GSS Client Unwrap
158
+ client . unwrap ( payload , ( err , response ) => {
159
+ if ( err ) return callback ( err ) ;
160
+
161
+ // Wrap the response
162
+ client . wrap ( response || '' , { user } , ( err , wrapped ) => {
163
+ if ( err ) return callback ( err ) ;
164
+
165
+ // Return the payload
166
+ callback ( undefined , wrapped ) ;
167
+ } ) ;
168
+ } ) ;
169
+ }
170
+
171
+ function performGssapiCanonicalizeHostName (
172
+ host : string ,
173
+ mechanismProperties : MechanismProperties ,
174
+ callback : Callback < string >
175
+ ) : void {
176
+ if ( ! mechanismProperties . gssapiCanonicalizeHostName ) return callback ( undefined , host ) ;
177
+
178
+ // Attempt to resolve the host name
179
+ dns . resolveCname ( host , ( err , r ) => {
180
+ if ( err ) return callback ( err ) ;
181
+
182
+ // Get the first resolve host id
183
+ if ( Array . isArray ( r ) && r . length > 0 ) {
184
+ return callback ( undefined , r [ 0 ] ) ;
185
+ }
186
+
187
+ callback ( undefined , host ) ;
188
+ } ) ;
189
+ }
0 commit comments