diff --git a/ql/src/experimental/CWE-352/ConstantOauth2State.qhelp b/ql/src/experimental/CWE-352/ConstantOauth2State.qhelp new file mode 100644 index 00000000000..dc15b5de1d9 --- /dev/null +++ b/ql/src/experimental/CWE-352/ConstantOauth2State.qhelp @@ -0,0 +1,26 @@ + + + +

+ OAuth 2.0 clients must implement CSRF protection for the redirection URI, which is typically accomplished by including a "state" value that binds the request to + the user's authenticated state. The Go OAuth 2.0 library allows to specify a "state" value which is then included in the auth code URL, and then provided back by the remote authentication server in the redirect callback, from where it must be validated; failure to do so makes the client susceptible to an CSRF attack. +

+
+ +

+ Always include a unique, non-guessable state value (provided to the call to AuthCodeURL function) that is also bound to the user's authenticated state with each authentication request, and then validated in the redirect callback. +

+
+ +

+ The first example shows you the use of a constant state (bad). +

+ +

+ The second example shows a better implementation idea. +

+ +
+
diff --git a/ql/src/experimental/CWE-352/ConstantOauth2State.ql b/ql/src/experimental/CWE-352/ConstantOauth2State.ql new file mode 100644 index 00000000000..96c095f637e --- /dev/null +++ b/ql/src/experimental/CWE-352/ConstantOauth2State.ql @@ -0,0 +1,101 @@ +/** + * @name Use of constant `state` value in OAuth 2.0 URL. + * @description Using a constant value for the `state` in the OAuth 2.0 URL makes the application + * susceptible to CSRF attacks. + * @kind path-problem + * @problem.severity error + * @id go/constant-oauth2-state + * @tags security + * external/cwe/cwe-352 + */ + +import go +import DataFlow::PathGraph + +/** + * A method that creates a new URL that will send the user + * to the OAuth 2.0 authorization dialog of the provider. + */ +class AuthCodeURL extends Method { + AuthCodeURL() { this.hasQualifiedName("golang.org/x/oauth2", "Config", "AuthCodeURL") } +} + +/** + * A flow of a constant string value to a call to AuthCodeURL as the + * `state` parameter. + */ +class ConstantStateFlowConf extends DataFlow::Configuration { + ConstantStateFlowConf() { this = "ConstantStateFlowConf" } + + predicate isSource(DataFlow::Node source, Literal state) { + state.isConst() and source.asExpr() = state + } + + predicate isSink(DataFlow::Node sink, DataFlow::CallNode call) { + exists(AuthCodeURL m | call = m.getACall() | sink = call.getArgument(0)) + } + + override predicate isSource(DataFlow::Node source) { isSource(source, _) } + + override predicate isSink(DataFlow::Node sink) { isSink(sink, _) } +} + +/** A flow to a printer function of the fmt package. */ +class FlowToPrint extends DataFlow::Configuration { + FlowToPrint() { this = "FlowToPrint" } + + predicate isSource(DataFlow::Node source, DataFlow::CallNode call) { + exists(AuthCodeURL m | call = m.getACall() | source = call.getResult()) + } + + predicate isSink(DataFlow::Node sink, DataFlow::CallNode call) { + exists(Fmt::Printer printer | call = printer.getACall() | sink = call.getArgument(_)) + } + + override predicate isSource(DataFlow::Node source) { isSource(source, _) } + + override predicate isSink(DataFlow::Node sink) { isSink(sink, _) } +} + +/** Holds if the provided CallNode's result flows to a Printer call as argument. */ +predicate resultFlowsToPrinter(DataFlow::CallNode authCodeURLCall) { + exists(FlowToPrint cfg, DataFlow::PathNode source, DataFlow::PathNode sink | + cfg.hasFlowPath(source, sink) and + cfg.isSource(source.getNode(), authCodeURLCall) + ) +} + +/** + * Holds if the provided CallNode is within the same root as a call + * to a scanner that reads from os.Stdin. + */ +predicate rootContainsCallToStdinScanner(DataFlow::CallNode authCodeURLCall) { + exists(Fmt::ScannerCall scannerCall | scannerCall.getRoot() = authCodeURLCall.getRoot()) + or + exists(Fmt::FScannerCall fScannerCall | + fScannerCall.getReader() = any(ValueEntity v | v.hasQualifiedName("os", "Stdin")).getARead() and + fScannerCall.getRoot() = authCodeURLCall.getRoot() + ) +} + +/** + * Holds if the authCodeURLCall seems to be done within a terminal + * because there are calls to a Printer (fmt.Println and similar), + * and a call to a Scanner (fmt.Scan and similar), + * all of which are typically done within a terminal session. + */ +predicate seemsLikeDoneWithinATerminal(DataFlow::CallNode authCodeURLCall) { + resultFlowsToPrinter(authCodeURLCall) and + rootContainsCallToStdinScanner(authCodeURLCall) +} + +from + ConstantStateFlowConf cfg, DataFlow::PathNode source, DataFlow::PathNode sink, + DataFlow::CallNode sinkCall +where + cfg.hasFlowPath(source, sink) and + cfg.isSink(sink.getNode(), sinkCall) and + // Exclude cases that seem to be oauth flows done from within a terminal: + not seemsLikeDoneWithinATerminal(sinkCall) +select sink.getNode(), source, sink, "Using a constant $@ to create oauth2 URLs.", source.getNode(), + "state string" diff --git a/ql/src/experimental/CWE-352/ConstantOauth2StateBad.go b/ql/src/experimental/CWE-352/ConstantOauth2StateBad.go new file mode 100644 index 00000000000..422736fc610 --- /dev/null +++ b/ql/src/experimental/CWE-352/ConstantOauth2StateBad.go @@ -0,0 +1,24 @@ +package main + +import ( + "golang.org/x/oauth2" +) + +func main() {} + +var stateStringVar = "state" + +func badWithStringLiteralState() { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringVar) + // ... +} diff --git a/ql/src/experimental/CWE-352/ConstantOauth2StateBetter.go b/ql/src/experimental/CWE-352/ConstantOauth2StateBetter.go new file mode 100644 index 00000000000..50d587dca7f --- /dev/null +++ b/ql/src/experimental/CWE-352/ConstantOauth2StateBetter.go @@ -0,0 +1,35 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "net/http" + + "golang.org/x/oauth2" +) + +func betterWithVariableStateReturned(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + state := generateStateOauthCookie(w) + url := conf.AuthCodeURL(state) + _ = url + // ... +} +func generateStateOauthCookie(w http.ResponseWriter) string { + b := make([]byte, 128) + rand.Read(b) + // TODO: save the state string to cookies or HTML storage, + // and bind it to the authenticated status of the user. + state := base64.URLEncoding.EncodeToString(b) + + return state +} diff --git a/ql/src/semmle/go/frameworks/Stdlib.qll b/ql/src/semmle/go/frameworks/Stdlib.qll index 8c4ef5b9017..29d58b89664 100644 --- a/ql/src/semmle/go/frameworks/Stdlib.qll +++ b/ql/src/semmle/go/frameworks/Stdlib.qll @@ -94,7 +94,7 @@ module Fmt { } /** The `Print` function or one of its variants. */ - private class Printer extends Function { + class Printer extends Function { Printer() { this.hasQualifiedName("fmt", ["Print", "Printf", "Println"]) } } @@ -131,6 +131,35 @@ module Fmt { exists(int i | if getName() = "Sscanf" then i > 1 else i > 0 | output.isParameter(i)) } } + + /** The `Scan` function or one of its variants, all of which read from os.Stdin */ + class Scanner extends Function { + Scanner() { this.hasQualifiedName("fmt", ["Scan", "Scanf", "Scanln"]) } + } + + /** A call to a `Scanner`. */ + class ScannerCall extends DataFlow::CallNode { + ScannerCall() { this.getTarget() instanceof Scanner } + } + + /** + * The `Fscan` function or one of its variants, + * all of which read from a specified io.Reader + */ + class FScanner extends Function { + FScanner() { this.hasQualifiedName("fmt", ["Fscan", "Fscanf", "Fscanln"]) } + } + + /** A call to a `FScanner`. */ + class FScannerCall extends DataFlow::CallNode { + FScannerCall() { this.getTarget() instanceof FScanner } + + /** + * Returns the node corresponding to the io.Reader + * argument provided in the call. + */ + DataFlow::Node getReader() { result = this.getArgument(0) } + } } /** Provides models of commonly used functions in the `io` package. */ diff --git a/ql/test/experimental/CWE-352/ConstantOauth2State.expected b/ql/test/experimental/CWE-352/ConstantOauth2State.expected new file mode 100644 index 00000000000..ecd1543cd1a --- /dev/null +++ b/ql/test/experimental/CWE-352/ConstantOauth2State.expected @@ -0,0 +1,35 @@ +edges +| ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:48:26:48:41 | stateStringConst | +| ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:145:26:145:41 | stateStringConst | +| ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:167:26:167:41 | stateStringConst | +| ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:189:26:189:41 | stateStringConst | +| ConstantOauth2State.go:20:22:20:28 | "state" : string | ConstantOauth2State.go:63:26:63:39 | stateStringVar | +| ConstantOauth2State.go:78:11:78:25 | call to newFixedState : string | ConstantOauth2State.go:79:26:79:30 | state | +| ConstantOauth2State.go:84:9:84:15 | "state" : string | ConstantOauth2State.go:78:11:78:25 | call to newFixedState : string | +| ConstantOauth2State.go:145:9:145:42 | call to AuthCodeURL : string | ConstantOauth2State.go:146:54:146:56 | url | +| ConstantOauth2State.go:167:9:167:42 | call to AuthCodeURL : string | ConstantOauth2State.go:168:54:168:56 | url | +| ConstantOauth2State.go:189:9:189:42 | call to AuthCodeURL : string | ConstantOauth2State.go:190:28:190:30 | url | +nodes +| ConstantOauth2State.go:18:26:18:32 | "state" : string literal | semmle.label | "state" : string literal | +| ConstantOauth2State.go:20:22:20:28 | "state" : string | semmle.label | "state" : string | +| ConstantOauth2State.go:33:26:33:32 | "state" | semmle.label | "state" | +| ConstantOauth2State.go:48:26:48:41 | stateStringConst | semmle.label | stateStringConst | +| ConstantOauth2State.go:63:26:63:39 | stateStringVar | semmle.label | stateStringVar | +| ConstantOauth2State.go:78:11:78:25 | call to newFixedState : string | semmle.label | call to newFixedState : string | +| ConstantOauth2State.go:79:26:79:30 | state | semmle.label | state | +| ConstantOauth2State.go:84:9:84:15 | "state" : string | semmle.label | "state" : string | +| ConstantOauth2State.go:145:9:145:42 | call to AuthCodeURL : string | semmle.label | call to AuthCodeURL : string | +| ConstantOauth2State.go:145:26:145:41 | stateStringConst | semmle.label | stateStringConst | +| ConstantOauth2State.go:146:54:146:56 | url | semmle.label | url | +| ConstantOauth2State.go:167:9:167:42 | call to AuthCodeURL : string | semmle.label | call to AuthCodeURL : string | +| ConstantOauth2State.go:167:26:167:41 | stateStringConst | semmle.label | stateStringConst | +| ConstantOauth2State.go:168:54:168:56 | url | semmle.label | url | +| ConstantOauth2State.go:189:9:189:42 | call to AuthCodeURL : string | semmle.label | call to AuthCodeURL : string | +| ConstantOauth2State.go:189:26:189:41 | stateStringConst | semmle.label | stateStringConst | +| ConstantOauth2State.go:190:28:190:30 | url | semmle.label | url | +#select +| ConstantOauth2State.go:33:26:33:32 | "state" | ConstantOauth2State.go:33:26:33:32 | "state" | ConstantOauth2State.go:33:26:33:32 | "state" | Using a constant $@ to create oauth2 URLs. | ConstantOauth2State.go:33:26:33:32 | "state" | state string | +| ConstantOauth2State.go:48:26:48:41 | stateStringConst | ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:48:26:48:41 | stateStringConst | Using a constant $@ to create oauth2 URLs. | ConstantOauth2State.go:18:26:18:32 | "state" | state string | +| ConstantOauth2State.go:63:26:63:39 | stateStringVar | ConstantOauth2State.go:20:22:20:28 | "state" : string | ConstantOauth2State.go:63:26:63:39 | stateStringVar | Using a constant $@ to create oauth2 URLs. | ConstantOauth2State.go:20:22:20:28 | "state" | state string | +| ConstantOauth2State.go:79:26:79:30 | state | ConstantOauth2State.go:84:9:84:15 | "state" : string | ConstantOauth2State.go:79:26:79:30 | state | Using a constant $@ to create oauth2 URLs. | ConstantOauth2State.go:84:9:84:15 | "state" | state string | +| ConstantOauth2State.go:189:26:189:41 | stateStringConst | ConstantOauth2State.go:18:26:18:32 | "state" : string literal | ConstantOauth2State.go:189:26:189:41 | stateStringConst | Using a constant $@ to create oauth2 URLs. | ConstantOauth2State.go:18:26:18:32 | "state" | state string | diff --git a/ql/test/experimental/CWE-352/ConstantOauth2State.go b/ql/test/experimental/CWE-352/ConstantOauth2State.go new file mode 100644 index 00000000000..b3200670fb9 --- /dev/null +++ b/ql/test/experimental/CWE-352/ConstantOauth2State.go @@ -0,0 +1,192 @@ +package main + +//go:generate depstubber -vendor golang.org/x/oauth2 Config,Endpoint + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "log" + "net/http" + "os" + + "golang.org/x/oauth2" +) + +func main() {} + +const stateStringConst = "state" + +var stateStringVar = "state" + +func badWithStringLiteralState(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL("state") // BAD + _ = url + // ... +} +func badWithConstState(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringConst) // BAD + _ = url + // ... +} +func badWithFixedVarState(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringVar) // BAD + _ = url + // ... +} +func badWithFixedStateReturned(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + state := newFixedState() + url := conf.AuthCodeURL(state) // BAD + _ = url + // ... +} +func newFixedState() string { + return "state" +} + +func betterWithVariableStateReturned(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + state := generateStateOauthCookie(w) + url := conf.AuthCodeURL(state) // OK, because the state is not a constant. + _ = url + // ... +} +func generateStateOauthCookie(w http.ResponseWriter) string { + b := make([]byte, 128) + rand.Read(b) + state := base64.URLEncoding.EncodeToString(b) + // TODO: save the state string to cookies or HTML storage. + return state +} +func okWithMixedVarState(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + state := fmt.Sprintf("%s-%s", stateStringVar, NewCSRFToken()) + + url := conf.AuthCodeURL(state) // OK, because the state is not a constant. + _ = url + // ... +} + +func NewCSRFToken() string { + b := make([]byte, 128) + rand.Read(b) + randomToken := base64.URLEncoding.EncodeToString(b) + return randomToken +} +func okWithConstStatePrinter(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringConst) // OK, because we're supposedly not exposed to the web, but within a terminal. + fmt.Printf("Visit the URL for the auth dialog: %v", url) + // ... + + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatal(err) + } + _ = code + // ... +} +func okWithConstStateFPrinter(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringConst) // OK, because we're supposedly not exposed to the web, but within a terminal. + fmt.Printf("Visit the URL for the auth dialog: %v", url) + // ... + + var code string + if _, err := fmt.Fscan(os.Stdin, &code); err != nil { + log.Fatal(err) + } + _ = code + // ... +} +func badWithConstStatePrinter(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringConst) // BAD + fmt.Printf("LOG: URL %v", url) + // ... +} diff --git a/ql/test/experimental/CWE-352/ConstantOauth2State.qlref b/ql/test/experimental/CWE-352/ConstantOauth2State.qlref new file mode 100644 index 00000000000..82c080c7754 --- /dev/null +++ b/ql/test/experimental/CWE-352/ConstantOauth2State.qlref @@ -0,0 +1 @@ +experimental/CWE-352/ConstantOauth2State.ql diff --git a/ql/test/experimental/CWE-352/go.mod b/ql/test/experimental/CWE-352/go.mod new file mode 100644 index 00000000000..037451d4bd2 --- /dev/null +++ b/ql/test/experimental/CWE-352/go.mod @@ -0,0 +1,5 @@ +module custom-queries-go-tests/constant-oauth2-state + +go 1.14 + +require golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/LICENSE b/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/stub.go b/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/stub.go new file mode 100644 index 00000000000..d4012cb20d9 --- /dev/null +++ b/ql/test/experimental/CWE-352/vendor/golang.org/x/oauth2/stub.go @@ -0,0 +1,81 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for golang.org/x/oauth2, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: golang.org/x/oauth2 (exports: Config,Endpoint; functions: ) + +// Package oauth2 is a stub of golang.org/x/oauth2, generated by depstubber. +package oauth2 + +import ( + context "context" + http "net/http" + time "time" +) + +type AuthCodeOption interface{} + +type AuthStyle int + +type Config struct { + ClientID string + ClientSecret string + Endpoint Endpoint + RedirectURL string + Scopes []string +} + +func (_ *Config) AuthCodeURL(_ string, _ ...AuthCodeOption) string { + return "" +} + +func (_ *Config) Client(_ context.Context, _ *Token) *http.Client { + return nil +} + +func (_ *Config) Exchange(_ context.Context, _ string, _ ...AuthCodeOption) (*Token, error) { + return nil, nil +} + +func (_ *Config) PasswordCredentialsToken(_ context.Context, _ string, _ string) (*Token, error) { + return nil, nil +} + +func (_ *Config) TokenSource(_ context.Context, _ *Token) TokenSource { + return nil +} + +type Endpoint struct { + AuthURL string + TokenURL string + AuthStyle AuthStyle +} + +type Token struct { + AccessToken string + TokenType string + RefreshToken string + Expiry time.Time +} + +func (_ *Token) Extra(_ string) interface{} { + return nil +} + +func (_ *Token) SetAuthHeader(_ *http.Request) {} + +func (_ *Token) Type() string { + return "" +} + +func (_ *Token) Valid() bool { + return false +} + +func (_ *Token) WithExtra(_ interface{}) *Token { + return nil +} + +type TokenSource interface { + Token() (*Token, error) +} diff --git a/ql/test/experimental/CWE-352/vendor/modules.txt b/ql/test/experimental/CWE-352/vendor/modules.txt new file mode 100644 index 00000000000..5f8149cc625 --- /dev/null +++ b/ql/test/experimental/CWE-352/vendor/modules.txt @@ -0,0 +1,3 @@ +# golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d +## explicit +golang.org/x/oauth2