1
1
package amqp
2
2
3
+ import (
4
+ "fmt"
5
+ )
6
+
3
7
// SASL Codes
4
8
const (
5
9
codeSASLOK saslCode = iota // Connection authentication succeeded.
@@ -13,6 +17,7 @@ const (
13
17
const (
14
18
saslMechanismPLAIN symbol = "PLAIN"
15
19
saslMechanismANONYMOUS symbol = "ANONYMOUS"
20
+ saslMechanismXOAUTH2 symbol = "XOAUTH2"
16
21
)
17
22
18
23
type saslCode uint8
@@ -92,3 +97,123 @@ func ConnSASLAnonymous() ConnOption {
92
97
return nil
93
98
}
94
99
}
100
+
101
+ // ConnSASLXOAUTH2 enables SASL XOAUTH2 authentication for the connection.
102
+ //
103
+ // The saslMaxFrameSizeOverride parameter allows the limit that governs the maximum frame size this client will allow
104
+ // itself to generate to be raised for the sasl-init frame only. Set this when the size of the size of the SASL XOAUTH2
105
+ // initial client response (which contains the username and bearer token) would otherwise breach the 512 byte min-max-frame-size
106
+ // (http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#definition-MIN-MAX-FRAME-SIZE). Pass -1
107
+ // to keep the default.
108
+ //
109
+ // SASL XOAUTH2 transmits the bearer in plain text and should only be used
110
+ // on TLS/SSL enabled connection.
111
+ func ConnSASLXOAUTH2 (username , bearer string , saslMaxFrameSizeOverride uint32 ) ConnOption {
112
+ return func (c * conn ) error {
113
+ // make handlers map if no other mechanism has
114
+ if c .saslHandlers == nil {
115
+ c .saslHandlers = make (map [symbol ]stateFunc )
116
+ }
117
+
118
+ response , err := saslXOAUTH2InitialResponse (username , bearer )
119
+ if err != nil {
120
+ return err
121
+ }
122
+
123
+ handler := saslXOAUTH2Handler {
124
+ conn : c ,
125
+ maxFrameSizeOverride : saslMaxFrameSizeOverride ,
126
+ response : response ,
127
+ }
128
+ // add the handler the the map
129
+ c .saslHandlers [saslMechanismXOAUTH2 ] = handler .init
130
+ return nil
131
+ }
132
+ }
133
+
134
+ type saslXOAUTH2Handler struct {
135
+ conn * conn
136
+ maxFrameSizeOverride uint32
137
+ response []byte
138
+ errorResponse []byte // https://developers.google.com/gmail/imap/xoauth2-protocol#error_response
139
+ }
140
+
141
+ func (s saslXOAUTH2Handler ) init () stateFunc {
142
+ originalPeerMaxFrameSize := s .conn .peerMaxFrameSize
143
+ if s .maxFrameSizeOverride > s .conn .peerMaxFrameSize {
144
+ s .conn .peerMaxFrameSize = s .maxFrameSizeOverride
145
+ }
146
+ s .conn .err = s .conn .writeFrame (frame {
147
+ type_ : frameTypeSASL ,
148
+ body : & saslInit {
149
+ Mechanism : saslMechanismXOAUTH2 ,
150
+ InitialResponse : s .response ,
151
+ },
152
+ })
153
+ s .conn .peerMaxFrameSize = originalPeerMaxFrameSize
154
+ if s .conn .err != nil {
155
+ return nil
156
+ }
157
+
158
+ return s .step
159
+ }
160
+
161
+ func (s saslXOAUTH2Handler ) step () stateFunc {
162
+ // read challenge or outcome frame
163
+ fr , err := s .conn .readFrame ()
164
+ if err != nil {
165
+ s .conn .err = err
166
+ return nil
167
+ }
168
+
169
+ switch v := fr .body .(type ) {
170
+ case * saslOutcome :
171
+ // check if auth succeeded
172
+ if v .Code != codeSASLOK {
173
+ s .conn .err = errorErrorf ("SASL XOAUTH2 auth failed with code %#00x: %s : %s" ,
174
+ v .Code , v .AdditionalData , s .errorResponse )
175
+ return nil
176
+ }
177
+
178
+ // return to c.negotiateProto
179
+ s .conn .saslComplete = true
180
+ return s .conn .negotiateProto
181
+ case * saslChallenge :
182
+ if s .errorResponse == nil {
183
+ s .errorResponse = v .Challenge
184
+
185
+ // The SASL protocol requires clients to send an empty response to this challenge.
186
+ s .conn .err = s .conn .writeFrame (frame {
187
+ type_ : frameTypeSASL ,
188
+ body : & saslResponse {
189
+ Response : []byte {},
190
+ },
191
+ })
192
+ return s .step
193
+ } else {
194
+ s .conn .err = errorErrorf ("SASL XOAUTH2 unexpected additional error response received during " +
195
+ "exchange. Initial error response: %s, additional response: %s" , s .errorResponse , v .Challenge )
196
+ return nil
197
+ }
198
+ default :
199
+ s .conn .err = errorErrorf ("unexpected frame type %T" , fr .body )
200
+ return nil
201
+ }
202
+ }
203
+
204
+ func saslXOAUTH2InitialResponse (username string , bearer string ) ([]byte , error ) {
205
+ if len (bearer ) == 0 {
206
+ return []byte {}, fmt .Errorf ("unacceptable bearer token" )
207
+ }
208
+ for _ , char := range bearer {
209
+ if char < '\x20' || char > '\x7E' {
210
+ return []byte {}, fmt .Errorf ("unacceptable bearer token" )
211
+ }
212
+ }
213
+ for _ , char := range username {
214
+ if char == '\x01' {
215
+ return []byte {}, fmt .Errorf ("unacceptable username" )
216
+ }
217
+ }
218
+ return []byte ("user=" + username + "\x01 auth=Bearer " + bearer + "\x01 \x01 " ), nil
219
+ }
0 commit comments