github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/x/mongo/driver/auth/sasl.go (about) 1 // 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 7 package auth 8 9 import ( 10 "context" 11 "fmt" 12 13 "go.mongodb.org/mongo-driver/bson" 14 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" 15 "go.mongodb.org/mongo-driver/x/mongo/driver" 16 "go.mongodb.org/mongo-driver/x/mongo/driver/operation" 17 ) 18 19 // SaslClient is the client piece of a sasl conversation. 20 type SaslClient interface { 21 Start() (string, []byte, error) 22 Next(challenge []byte) ([]byte, error) 23 Completed() bool 24 } 25 26 // SaslClientCloser is a SaslClient that has resources to clean up. 27 type SaslClientCloser interface { 28 SaslClient 29 Close() 30 } 31 32 // ExtraOptionsSaslClient is a SaslClient that appends options to the saslStart command. 33 type ExtraOptionsSaslClient interface { 34 StartCommandOptions() bsoncore.Document 35 } 36 37 // saslConversation represents a SASL conversation. This type implements the SpeculativeConversation interface so the 38 // conversation can be executed in multi-step speculative fashion. 39 type saslConversation struct { 40 client SaslClient 41 source string 42 mechanism string 43 speculative bool 44 } 45 46 var _ SpeculativeConversation = (*saslConversation)(nil) 47 48 func newSaslConversation(client SaslClient, source string, speculative bool) *saslConversation { 49 authSource := source 50 if authSource == "" { 51 authSource = defaultAuthDB 52 } 53 return &saslConversation{ 54 client: client, 55 source: authSource, 56 speculative: speculative, 57 } 58 } 59 60 // FirstMessage returns the first message to be sent to the server. This message contains a "db" field so it can be used 61 // for speculative authentication. 62 func (sc *saslConversation) FirstMessage() (bsoncore.Document, error) { 63 var payload []byte 64 var err error 65 sc.mechanism, payload, err = sc.client.Start() 66 if err != nil { 67 return nil, err 68 } 69 70 saslCmdElements := [][]byte{ 71 bsoncore.AppendInt32Element(nil, "saslStart", 1), 72 bsoncore.AppendStringElement(nil, "mechanism", sc.mechanism), 73 bsoncore.AppendBinaryElement(nil, "payload", 0x00, payload), 74 } 75 if sc.speculative { 76 // The "db" field is only appended for speculative auth because the hello command is executed against admin 77 // so this is needed to tell the server the user's auth source. For a non-speculative attempt, the SASL commands 78 // will be executed against the auth source. 79 saslCmdElements = append(saslCmdElements, bsoncore.AppendStringElement(nil, "db", sc.source)) 80 } 81 if extraOptionsClient, ok := sc.client.(ExtraOptionsSaslClient); ok { 82 optionsDoc := extraOptionsClient.StartCommandOptions() 83 saslCmdElements = append(saslCmdElements, bsoncore.AppendDocumentElement(nil, "options", optionsDoc)) 84 } 85 86 return bsoncore.BuildDocumentFromElements(nil, saslCmdElements...), nil 87 } 88 89 type saslResponse struct { 90 ConversationID int `bson:"conversationId"` 91 Code int `bson:"code"` 92 Done bool `bson:"done"` 93 Payload []byte `bson:"payload"` 94 } 95 96 // Finish completes the conversation based on the first server response to authenticate the given connection. 97 func (sc *saslConversation) Finish(ctx context.Context, cfg *Config, firstResponse bsoncore.Document) error { 98 if closer, ok := sc.client.(SaslClientCloser); ok { 99 defer closer.Close() 100 } 101 102 var saslResp saslResponse 103 err := bson.Unmarshal(firstResponse, &saslResp) 104 if err != nil { 105 fullErr := fmt.Errorf("unmarshal error: %v", err) 106 return newError(fullErr, sc.mechanism) 107 } 108 109 cid := saslResp.ConversationID 110 var payload []byte 111 var rdr bsoncore.Document 112 for { 113 if saslResp.Code != 0 { 114 return newError(err, sc.mechanism) 115 } 116 117 if saslResp.Done && sc.client.Completed() { 118 return nil 119 } 120 121 payload, err = sc.client.Next(saslResp.Payload) 122 if err != nil { 123 return newError(err, sc.mechanism) 124 } 125 126 if saslResp.Done && sc.client.Completed() { 127 return nil 128 } 129 130 doc := bsoncore.BuildDocumentFromElements(nil, 131 bsoncore.AppendInt32Element(nil, "saslContinue", 1), 132 bsoncore.AppendInt32Element(nil, "conversationId", int32(cid)), 133 bsoncore.AppendBinaryElement(nil, "payload", 0x00, payload), 134 ) 135 saslContinueCmd := operation.NewCommand(doc). 136 Database(sc.source). 137 Deployment(driver.SingleConnectionDeployment{cfg.Connection}). 138 ClusterClock(cfg.ClusterClock). 139 ServerAPI(cfg.ServerAPI) 140 141 err = saslContinueCmd.Execute(ctx) 142 if err != nil { 143 return newError(err, sc.mechanism) 144 } 145 rdr = saslContinueCmd.Result() 146 147 err = bson.Unmarshal(rdr, &saslResp) 148 if err != nil { 149 fullErr := fmt.Errorf("unmarshal error: %v", err) 150 return newError(fullErr, sc.mechanism) 151 } 152 } 153 } 154 155 // ConductSaslConversation runs a full SASL conversation to authenticate the given connection. 156 func ConductSaslConversation(ctx context.Context, cfg *Config, authSource string, client SaslClient) error { 157 // Create a non-speculative SASL conversation. 158 conversation := newSaslConversation(client, authSource, false) 159 160 saslStartDoc, err := conversation.FirstMessage() 161 if err != nil { 162 return newError(err, conversation.mechanism) 163 } 164 saslStartCmd := operation.NewCommand(saslStartDoc). 165 Database(authSource). 166 Deployment(driver.SingleConnectionDeployment{cfg.Connection}). 167 ClusterClock(cfg.ClusterClock). 168 ServerAPI(cfg.ServerAPI) 169 if err := saslStartCmd.Execute(ctx); err != nil { 170 return newError(err, conversation.mechanism) 171 } 172 173 return conversation.Finish(ctx, cfg, saslStartCmd.Result()) 174 }