├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation.md │ └── feature_request.md └── workflows │ ├── go.yml │ └── golangci-lint.yml ├── .gitignore ├── .golangci.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASES.md ├── SECURITY.md ├── ado └── build_test.yaml ├── apps ├── cache │ └── cache.go ├── confidential │ ├── confidential.go │ ├── confidential_test.go │ └── examples_test.go ├── design │ ├── design.md │ └── release.md ├── errors │ ├── error_design.md │ └── errors.go ├── internal │ ├── base │ │ ├── base.go │ │ ├── base_test.go │ │ └── storage │ │ │ ├── items.go │ │ │ ├── items_test.go │ │ │ ├── partitioned_storage.go │ │ │ ├── partitioned_storage_test.go │ │ │ ├── storage.go │ │ │ ├── storage_test.go │ │ │ └── testdata │ │ │ ├── test_serialized_cache.json │ │ │ ├── v1.0_cache.json │ │ │ └── v1.0_v1.1_cache.json │ ├── exported │ │ └── exported.go │ ├── json │ │ ├── design.md │ │ ├── json.go │ │ ├── json_test.go │ │ ├── mapslice.go │ │ ├── mapslice_test.go │ │ ├── marshal.go │ │ ├── marshal_test.go │ │ ├── struct.go │ │ ├── struct_test.go │ │ └── types │ │ │ └── time │ │ │ └── time.go │ ├── local │ │ ├── server.go │ │ └── server_test.go │ ├── mock │ │ └── mock.go │ ├── oauth │ │ ├── fake │ │ │ └── fake.go │ │ ├── oauth.go │ │ ├── oauth_test.go │ │ ├── ops │ │ │ ├── accesstokens │ │ │ │ ├── accesstokens.go │ │ │ │ ├── accesstokens_test.go │ │ │ │ ├── apptype_string.go │ │ │ │ └── tokens.go │ │ │ ├── authority │ │ │ │ ├── authority.go │ │ │ │ ├── authority_test.go │ │ │ │ └── authorizetype_string.go │ │ │ ├── internal │ │ │ │ ├── comm │ │ │ │ │ ├── comm.go │ │ │ │ │ ├── comm_test.go │ │ │ │ │ └── compress.go │ │ │ │ └── grant │ │ │ │ │ └── grant.go │ │ │ ├── ops.go │ │ │ └── wstrust │ │ │ │ ├── defs │ │ │ │ ├── endpointtype_string.go │ │ │ │ ├── mex_document_definitions.go │ │ │ │ ├── saml_assertion_definitions.go │ │ │ │ ├── version_string.go │ │ │ │ ├── wstrust_endpoint.go │ │ │ │ └── wstrust_mex_document.go │ │ │ │ ├── wstrust.go │ │ │ │ └── wstrust_test.go │ │ └── resolvers.go │ ├── options │ │ └── options.go │ ├── shared │ │ ├── shared.go │ │ └── shared_test.go │ └── version │ │ └── version.go ├── managedidentity │ ├── azure_ml.go │ ├── cloud_shell.go │ ├── managedidentity.go │ ├── managedidentity_test.go │ ├── servicefabric.go │ └── servicefabric_test.go ├── public │ ├── example_test.go │ ├── public.go │ └── public_test.go ├── testdata │ ├── test-cert-chain-reverse.pem │ ├── test-cert-chain.pem │ └── test-cert.pem └── tests │ ├── benchmarks │ └── confidential.go │ ├── devapps │ ├── README.md │ ├── authorization_code_sample.go │ ├── client_certificate_sample.go │ ├── client_secret_sample.go │ ├── confidential_auth_code_sample.go │ ├── confidential_config.json │ ├── config.json │ ├── device_code_flow_sample.go │ ├── main.go │ ├── managedidentity │ │ └── docs │ │ │ └── msi_manual_testing.md │ ├── sample_cache_accessor.go │ ├── sample_utils.go │ ├── serialized_cache.json │ └── username_password_sample.go │ ├── integration │ ├── README.md │ ├── cache_accessor.go │ ├── integration_test.go │ └── serialized_cache_1.1.1.json │ └── performance │ └── performance_test.go ├── changelog.md ├── docs └── managedidentity_public_api.md ├── go.mod └── go.sum /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Please do NOT file bugs without filling in this form. 4 | title: '[Bug] ' 5 | labels: ["untriaged", "needs attention"] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Which version of MSAL Go are you using?** 11 | Note that to get help, you need to run the latest version. 12 | 13 | 14 | **Where is the issue?** 15 | * Public client 16 | * [ ] Device code flow 17 | * [ ] Username/Password (ROPC grant) 18 | * [ ] Authorization code flow 19 | * Confidential client 20 | * [ ] Authorization code flow 21 | * [ ] Client credentials: 22 | * [ ] client secret 23 | * [ ] client certificate 24 | * Token cache serialization 25 | * [ ] In-memory cache 26 | * Other (please describe) 27 | 28 | **Is this a new or an existing app?** 29 | 34 | 35 | **What version of Go are you using (`go version`)?** 36 | 37 |
38 | $ go version 39 |40 | 41 | **What operating system and processor architecture are you using (`go env`)?** 42 | 43 |
go env
Output44 | $ go env 45 | 46 |
var your = (code) => here;
51 |
52 | **Expected behavior**
53 | A clear and concise description of what you expected to happen (or code).
54 |
55 | **Actual behavior**
56 | A clear and concise description of what happens, e.g. an exception is thrown, UI freezes.
57 |
58 | **Possible solution**
59 |
60 |
61 | **Additional context / logs / screenshots**
62 | Add any other context about the problem here, such as logs and screenshots.
63 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation
3 | about: Suggest a change to the documentation.
4 | title: '[Documentation] '
5 | labels: documentation
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Documentation related to component
11 |
12 |
13 | ### Please check all that apply
14 |
15 | - [ ] typo
16 | - [ ] documentation doesn't exist
17 | - [ ] documentation needs clarification
18 | - [ ] error(s) in the example
19 | - [ ] needs an example
20 |
21 | ### Description of the issue
22 |
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project.
4 | title: "[Feature Request] "
5 | labels: enhancement, Feature Request
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...].
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | # This guards against unknown PR until a community member vet it and label it.
8 | types: [ labeled ]
9 |
10 |
11 | jobs:
12 | build:
13 | name: Build
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | go: ["1.21", "1.22"]
20 |
21 | steps:
22 | - name: Set up Go 1.x
23 | uses: actions/setup-go@v2
24 | with:
25 | go-version: ${{ matrix.go }}
26 | id: go
27 |
28 | - name: Check out code into the Go module directory
29 | uses: actions/checkout@v2
30 |
31 | - name: Get dependencies
32 | run: go get -v -t -d ./...
33 |
34 | # designed to only run on linux
35 | # - name: Format Check
36 | # run: if [ $(gofmt -l -s . | wc -l) -ne 0 ]; then echo "fmt failed"; exit 1; fi
37 |
38 | - name: Build
39 | run: go build ./apps/...
40 |
41 | - name: Unit Tests
42 | run: go test -race -short ./apps/cache/... ./apps/confidential/... ./apps/public/... ./apps/internal/... ./apps/managedidentity/...
43 | # Intergration tests runs on ADO
44 | # - name: Integration Tests
45 | # run: go test -race ./apps/tests/integration/...
46 | # env :
47 | # clientId: ${{ secrets.LAB_APP_CLIENT_ID }}
48 | # clientSecret: ${{ secrets.LAB_APP_CLIENT_SECRET }}
49 | # oboConfidentialClientId: ${{ secrets.OBO_CONFIDENTIAL_APP_CLIENT_ID }}
50 | # oboConfidentialClientSecret: ${{ secrets.OBO_CONFIDENTIAL_APP_CLIENT_SECRET }}
51 | # oboPublicClientId: ${{ secrets.OBO_PUBLIC_APP_CLIENT_ID }}
52 | # CI: ${{secrets.ENABLECI}}
53 |
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | branches:
7 | - main
8 | pull_request:
9 | # This guards against unknown PR until a community member vet it and label it.
10 | types: [ labeled ]
11 |
12 | jobs:
13 | golangci:
14 | name: lint
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/setup-go@v3
18 | with:
19 | go-version: "1.20"
20 | - uses: actions/checkout@v3
21 | - name: golangci-lint
22 | uses: golangci/golangci-lint-action@v3
23 | with:
24 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
25 | version: v1.51
26 |
27 | # Optional: golangci-lint command line arguments.
28 | # args: --issues-exit-code=0
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | *.golangci.yml
8 | *.swp
9 | *.pprof
10 |
11 | # OSX specific os files
12 | *.DS_Store
13 |
14 | # Test binary, build with `go test -c`
15 | *.test
16 |
17 | # Output of the go coverage tool, specifically when used with LiteIDE
18 | *.out
19 |
20 | pkg/
21 | github.com/
22 | golang.org/
23 | .vscode/
24 | .idea/
25 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | # enabled in addition to default
3 | enable:
4 | - gosec
5 |
6 | issues:
7 | # Excluding configuration per-path, per-linter, per-text and per-source
8 | exclude-rules:
9 | # Exclude some linters from running on tests files.
10 | - path: _test\.go
11 | linters:
12 | - gosec
13 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @bgavrilMS @rayluo
2 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Microsoft Authentication Library for Go welcomes new contributors
2 |
3 | This document will guide you through the process.
4 |
5 | ## Contributor License agreement
6 |
7 | Please visit [https://cla.microsoft.com/](https://cla.microsoft.com/) and sign the Contributor License
8 | Agreement. You only need to do that once. We can not look at your code until you've submitted this request.
9 |
10 | ## FORK
11 |
12 | Fork the project [on GitHub](https://github.com/AzureAD/microsoft-authentication-library-for-go) and check out
13 | your copy.
14 |
15 | Example for MSAL Go:
16 |
17 | ```
18 | $ git clone git@github.com:username/microsoft-authentication-library-for-go.git
19 | $ cd microsoft-authentication-library-for-go
20 | $ git remote add upstream git@github.com:AzureAD/microsoft-authentication-library-for-go.git
21 | ```
22 |
23 | ## Setup, Building and Testing
24 |
25 | Please see the [Build & Run](https://github.com/AzureAD/microsoft-authentication-library-for-go/wiki/build-and-test) wiki page.
26 |
27 | ## Decide on which branch to create
28 |
29 | **Bug fixes for the current stable version need to go to 'main' branch.**
30 |
31 | If you need to contribute to a different branch, please contact us first (open an issue).
32 |
33 | All details after this point is standard - make sure your commits have nice messages, and prefer rebase to merge.
34 |
35 | In case of doubt, please open an issue in the [issue tracker](https://github.com/AzureAD/microsoft-authentication-library-for-go/issues).
36 |
37 | Especially do so if you plan to work on a major change in functionality. Nothing is more
38 | frustrating than seeing your hard work go to waste because your vision
39 | does not align with our goals for the SDK.
40 |
41 | ## Branch
42 |
43 | Okay, so you have decided on the proper branch. Create a feature branch
44 | and start hacking:
45 |
46 | ```
47 | $ git checkout -b my-feature-branch
48 | ```
49 |
50 | ## Commit
51 |
52 | Make sure git knows your name and email address:
53 |
54 | ```
55 | $ git config --global user.name "J. Random User"
56 | $ git config --global user.email "j.random.user@example.com"
57 | ```
58 |
59 | Writing good commit logs is important. A commit log should describe what
60 | changed and why. Follow these guidelines when writing one:
61 |
62 | 1. The first line should be 50 characters or less and contain a short
63 | description of the change prefixed with the name of the changed
64 | subsystem (e.g. "net: add localAddress and localPort to Socket").
65 | 2. Keep the second line blank.
66 | 3. Wrap all other lines at 72 columns.
67 |
68 | A good commit log looks like this:
69 |
70 | ```
71 | fix: explaining the commit in one line
72 |
73 | Body of commit message is a few lines of text, explaining things
74 | in more detail, possibly giving some background about the issue
75 | being fixed, etc etc.
76 |
77 | The body of the commit message can be several paragraphs, and
78 | please do proper word-wrap and keep columns shorter than about
79 | 72 characters or so. That way `git log` will show things
80 | nicely even when it is indented.
81 | ```
82 |
83 | The header line should be meaningful; it is what other people see when they
84 | run `git shortlog` or `git log --oneline`.
85 |
86 | Check the output of `git log --oneline files_that_you_changed` to find out
87 | what directories your changes touch.
88 |
89 | ### Rebase
90 |
91 | Use `git rebase` (not `git merge`) to sync your work from time to time.
92 |
93 | ```
94 | $ git fetch upstream
95 | $ git rebase upstream/v0.1 # or upstream/main
96 | ```
97 |
98 | ### Tests
99 |
100 | It's all standard stuff, but please note that you won't be able to run integration tests locally because they connect to a KeyVault to fetch some test users and passwords. The CI will run them for you.
101 |
102 | ### Push
103 |
104 | ```
105 | $ git push origin my-feature-branch
106 | ```
107 |
108 | Go to `https://github.com/username/microsoft-authentication-library-for-go` and select your feature branch. Click
109 | the 'Pull Request' button and fill out the form.
110 |
111 | Pull requests are usually reviewed within a few days. If there are comments
112 | to address, apply your changes in a separate commit and push that to your
113 | feature branch. Post a comment in the pull request afterwards; GitHub does
114 | not send out notifications when you add commits.
115 |
116 | [on GitHub]: https://github.com/AzureAD/microsoft-authentication-library-for-go
117 | [issue tracker]: https://github.com/AzureAD/microsoft-authentication-library-for-go/issues
118 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/RELEASES.md:
--------------------------------------------------------------------------------
1 | # Microsoft Identity SDK Versioning and Servicing FAQ
2 |
3 | We have adopted the semantic versioning flow that is industry standard for OSS projects. It gives the maximum amount of control on what risk you take with what versions. If you know how semantic versioning works with node.js, java, and ruby none of this will be new.
4 |
5 | ## Semantic Versioning and API stability promises
6 |
7 | Microsoft Identity libraries are independent open source libraries that are used by partners both internal and external to Microsoft. As with the rest of Microsoft, we have moved to a rapid iteration model where bugs are fixed daily and new versions are produced as required. To communicate these frequent changes to external partners and customers, we use semantic versioning for all our public Microsoft Identity SDK libraries. This follows the practices of other open source libraries on the internet. This allows us to support our downstream partners which will lock on certain versions for stability purposes, as well as providing for the distribution over NuGet, CocoaPods, and Maven.
8 |
9 | The semantics are: MAJOR.MINOR.PATCH (example 1.1.5)
10 |
11 | We will update our code distributions to use the latest PATCH semantic version number in order to make sure our customers and partners get the latest bug fixes. Downstream partner needs to pull the latest PATCH version. Most partners should try lock on the latest MINOR version number in their builds and accept any updates in the PATCH number.
12 |
13 | Using NuGet, this ensures all 1.1.0 to 1.1.x updates are included when building your code, but not 1.2.
14 |
15 | ```
16 | Authentication complete. You can return to the application. Feel free to close this browser tab.
27 | 28 | 29 | `) 30 | 31 | const failPage = ` 32 | 33 | 34 | 35 | 36 |Authentication failed. You can return to the application. Feel free to close this browser tab.
40 |Error details: error %s error_description: %s
41 | 42 | 43 | ` 44 | 45 | // Result is the result from the redirect. 46 | type Result struct { 47 | // Code is the code sent by the authority server. 48 | Code string 49 | // Err is set if there was an error. 50 | Err error 51 | } 52 | 53 | // Server is an HTTP server. 54 | type Server struct { 55 | // Addr is the address the server is listening on. 56 | Addr string 57 | resultCh chan Result 58 | s *http.Server 59 | reqState string 60 | } 61 | 62 | // New creates a local HTTP server and starts it. 63 | func New(reqState string, port int) (*Server, error) { 64 | var l net.Listener 65 | var err error 66 | var portStr string 67 | if port > 0 { 68 | // use port provided by caller 69 | l, err = net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) 70 | portStr = strconv.FormatInt(int64(port), 10) 71 | } else { 72 | // find a free port 73 | for i := 0; i < 10; i++ { 74 | l, err = net.Listen("tcp", "localhost:0") 75 | if err != nil { 76 | continue 77 | } 78 | addr := l.Addr().String() 79 | portStr = addr[strings.LastIndex(addr, ":")+1:] 80 | break 81 | } 82 | } 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | serv := &Server{ 88 | Addr: fmt.Sprintf("http://localhost:%s", portStr), 89 | s: &http.Server{Addr: "localhost:0", ReadHeaderTimeout: time.Second}, 90 | reqState: reqState, 91 | resultCh: make(chan Result, 1), 92 | } 93 | serv.s.Handler = http.HandlerFunc(serv.handler) 94 | 95 | if err := serv.start(l); err != nil { 96 | return nil, err 97 | } 98 | 99 | return serv, nil 100 | } 101 | 102 | func (s *Server) start(l net.Listener) error { 103 | go func() { 104 | err := s.s.Serve(l) 105 | if err != nil { 106 | select { 107 | case s.resultCh <- Result{Err: err}: 108 | default: 109 | } 110 | } 111 | }() 112 | 113 | return nil 114 | } 115 | 116 | // Result gets the result of the redirect operation. Once a single result is returned, the server 117 | // is shutdown. ctx deadline will be honored. 118 | func (s *Server) Result(ctx context.Context) Result { 119 | select { 120 | case <-ctx.Done(): 121 | return Result{Err: ctx.Err()} 122 | case r := <-s.resultCh: 123 | return r 124 | } 125 | } 126 | 127 | // Shutdown shuts down the server. 128 | func (s *Server) Shutdown() { 129 | // Note: You might get clever and think you can do this in handler() as a defer, you can't. 130 | _ = s.s.Shutdown(context.Background()) 131 | } 132 | 133 | func (s *Server) putResult(r Result) { 134 | select { 135 | case s.resultCh <- r: 136 | default: 137 | } 138 | } 139 | 140 | func (s *Server) handler(w http.ResponseWriter, r *http.Request) { 141 | q := r.URL.Query() 142 | 143 | headerErr := q.Get("error") 144 | if headerErr != "" { 145 | desc := html.EscapeString(q.Get("error_description")) 146 | escapedHeaderErr := html.EscapeString(headerErr) 147 | // Note: It is a little weird we handle some errors by not going to the failPage. If they all should, 148 | // change this to s.error() and make s.error() write the failPage instead of an error code. 149 | _, _ = w.Write([]byte(fmt.Sprintf(failPage, escapedHeaderErr, desc))) 150 | s.putResult(Result{Err: fmt.Errorf("%s", desc)}) 151 | 152 | return 153 | } 154 | 155 | respState := q.Get("state") 156 | switch respState { 157 | case s.reqState: 158 | case "": 159 | s.error(w, http.StatusInternalServerError, "server didn't send OAuth state") 160 | return 161 | default: 162 | s.error(w, http.StatusInternalServerError, "mismatched OAuth state, req(%s), resp(%s)", s.reqState, respState) 163 | return 164 | } 165 | 166 | code := q.Get("code") 167 | if code == "" { 168 | s.error(w, http.StatusInternalServerError, "authorization code missing in query string") 169 | return 170 | } 171 | 172 | _, _ = w.Write(okPage) 173 | s.putResult(Result{Code: code}) 174 | } 175 | 176 | func (s *Server) error(w http.ResponseWriter, code int, str string, i ...interface{}) { 177 | err := fmt.Errorf(str, i...) 178 | http.Error(w, err.Error(), code) 179 | s.putResult(Result{Err: err}) 180 | } 181 | -------------------------------------------------------------------------------- /apps/internal/local/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package local 5 | 6 | import ( 7 | "context" 8 | "io" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "testing" 13 | "time" 14 | 15 | "github.com/kylelemons/godebug/pretty" 16 | ) 17 | 18 | func TestServer(t *testing.T) { 19 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 20 | defer cancel() 21 | 22 | tests := []struct { 23 | desc string 24 | reqState string 25 | port int 26 | q url.Values 27 | failPage bool 28 | statusCode int 29 | }{ 30 | { 31 | desc: "Error: Query Values has 'error' key", 32 | reqState: "state", 33 | port: 0, 34 | q: url.Values{"state": []string{"state"}, "error": []string{"error"}}, 35 | statusCode: 200, 36 | failPage: true, 37 | }, 38 | { 39 | desc: "Error: Query Values missing 'state' key", 40 | reqState: "state", 41 | port: 0, 42 | q: url.Values{"code": []string{"code"}}, 43 | statusCode: http.StatusInternalServerError, 44 | }, 45 | { 46 | desc: "Error: Query Values missing had 'state' key value that was different that requested", 47 | reqState: "state", 48 | port: 0, 49 | q: url.Values{"state": []string{"etats"}, "code": []string{"code"}}, 50 | statusCode: http.StatusInternalServerError, 51 | }, 52 | { 53 | desc: "Error: Query Values missing 'code' key", 54 | reqState: "state", 55 | port: 0, 56 | q: url.Values{"state": []string{"state"}}, 57 | statusCode: http.StatusInternalServerError, 58 | }, 59 | { 60 | desc: "Success", 61 | reqState: "state", 62 | port: 0, 63 | q: url.Values{"state": []string{"state"}, "code": []string{"code"}}, 64 | statusCode: 200, 65 | }, 66 | } 67 | 68 | for _, test := range tests { 69 | serv, err := New(test.reqState, test.port) 70 | if err != nil { 71 | panic(err) 72 | } 73 | defer serv.Shutdown() 74 | 75 | if !strings.HasPrefix(serv.Addr, "http://localhost") { 76 | t.Fatalf("unexpected server address %s", serv.Addr) 77 | } 78 | u, err := url.Parse(serv.Addr) 79 | if err != nil { 80 | panic(err) 81 | } 82 | u.RawQuery = test.q.Encode() 83 | 84 | resp, err := http.DefaultClient.Do( 85 | &http.Request{ 86 | Method: "GET", 87 | URL: u, 88 | }, 89 | ) 90 | 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | if resp.StatusCode != test.statusCode { 96 | if test.statusCode == 200 { 97 | t.Errorf("TestServer(%s): got StatusCode == %d, want StatusCode == 200", test.desc, resp.StatusCode) 98 | res := serv.Result(ctx) 99 | if res.Err == nil { 100 | t.Errorf("TestServer(%s): Result.Err == nil, want Result.Err != nil", test.desc) 101 | } 102 | continue 103 | } 104 | t.Errorf("TestServer(%s): got StatusCode == %d, want StatusCode == %d", test.desc, resp.StatusCode, test.statusCode) 105 | res := serv.Result(ctx) 106 | if res.Err == nil { 107 | t.Errorf("TestServer(%s): Result.Err == nil, want Result.Err != nil", test.desc) 108 | } 109 | continue 110 | } 111 | if resp.StatusCode != 200 { 112 | continue 113 | } 114 | 115 | content, err := io.ReadAll(resp.Body) 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | if test.failPage { 121 | if !strings.Contains(string(content), "Authentication Failed") { 122 | t.Errorf("TestServer(%s): got okay page, want failed page", test.desc) 123 | } 124 | 125 | res := serv.Result(ctx) 126 | if res.Err == nil { 127 | t.Errorf("TestServer(%s): Result.Err == nil, want Result.Err != nil", test.desc) 128 | } 129 | continue 130 | } 131 | 132 | if !strings.Contains(string(content), "Authentication Complete") { 133 | t.Errorf("TestServer(%s): got failed page, okay page", test.desc) 134 | } 135 | 136 | res := serv.Result(ctx) 137 | if diff := pretty.Compare(Result{Code: "code"}, res); diff != "" { 138 | t.Errorf("TestServer(%s): -want/+got:\n%s", test.desc, diff) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /apps/internal/mock/mock.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package mock 5 | 6 | import ( 7 | "bytes" 8 | "encoding/base64" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 17 | ) 18 | 19 | type response struct { 20 | body []byte 21 | callback func(*http.Request) 22 | code int 23 | headers http.Header 24 | } 25 | 26 | type responseOption interface { 27 | apply(*response) 28 | } 29 | 30 | type respOpt func(*response) 31 | 32 | func (fn respOpt) apply(r *response) { 33 | fn(r) 34 | } 35 | 36 | // WithBody sets the HTTP response's body to the specified value. 37 | func WithBody(b []byte) responseOption { 38 | return respOpt(func(r *response) { 39 | r.body = b 40 | }) 41 | } 42 | 43 | // WithCallback sets a callback to invoke before returning the response. 44 | func WithCallback(callback func(*http.Request)) responseOption { 45 | return respOpt(func(r *response) { 46 | r.callback = callback 47 | }) 48 | } 49 | 50 | // WithHTTPHeader sets the HTTP headers of the response to the specified value. 51 | func WithHTTPHeader(header http.Header) responseOption { 52 | return respOpt(func(r *response) { 53 | r.headers = header 54 | }) 55 | } 56 | 57 | // WithHTTPStatusCode sets the HTTP statusCode of response to the specified value. 58 | func WithHTTPStatusCode(statusCode int) responseOption { 59 | return respOpt(func(r *response) { 60 | r.code = statusCode 61 | }) 62 | } 63 | 64 | // Client is a mock HTTP client that returns a sequence of responses. Use AppendResponse to specify the sequence. 65 | type Client struct { 66 | mu *sync.Mutex 67 | resp []response 68 | } 69 | 70 | func NewClient() *Client { 71 | return &Client{mu: &sync.Mutex{}} 72 | } 73 | 74 | func (c *Client) AppendResponse(opts ...responseOption) { 75 | c.mu.Lock() 76 | defer c.mu.Unlock() 77 | r := response{code: http.StatusOK, headers: http.Header{}} 78 | for _, o := range opts { 79 | o.apply(&r) 80 | } 81 | c.resp = append(c.resp, r) 82 | } 83 | 84 | func (c *Client) Do(req *http.Request) (*http.Response, error) { 85 | c.mu.Lock() 86 | defer c.mu.Unlock() 87 | if len(c.resp) == 0 { 88 | panic(fmt.Sprintf(`no response for "%s"`, req.URL.String())) 89 | } 90 | resp := c.resp[0] 91 | c.resp = c.resp[1:] 92 | if resp.callback != nil { 93 | resp.callback(req) 94 | } 95 | res := http.Response{Header: resp.headers, StatusCode: resp.code} 96 | res.Body = io.NopCloser(bytes.NewReader(resp.body)) 97 | return &res, nil 98 | } 99 | 100 | // CloseIdleConnections implements the comm.HTTPClient interface 101 | func (*Client) CloseIdleConnections() {} 102 | 103 | func GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo string, expiresIn, refreshIn int) []byte { 104 | // Start building the body with the common fields 105 | body := fmt.Sprintf( 106 | `{"access_token": "%s","expires_in": %d,"expires_on": %d,"token_type": "Bearer"`, 107 | accessToken, expiresIn, time.Now().Add(time.Duration(expiresIn)*time.Second).Unix(), 108 | ) 109 | 110 | // Conditionally add the "refresh_in" field if refreshIn is provided 111 | if refreshIn > 0 { 112 | body += fmt.Sprintf(`, "refresh_in":"%d"`, refreshIn) 113 | } 114 | 115 | // Add the optional fields if they are provided 116 | if clientInfo != "" { 117 | body += fmt.Sprintf(`, "client_info": "%s"`, clientInfo) 118 | } 119 | if idToken != "" { 120 | body += fmt.Sprintf(`, "id_token": "%s"`, idToken) 121 | } 122 | if refreshToken != "" { 123 | body += fmt.Sprintf(`, "refresh_token": "%s"`, refreshToken) 124 | } 125 | 126 | // Close the JSON string 127 | body += "}" 128 | 129 | return []byte(body) 130 | } 131 | 132 | func GetIDToken(tenant, issuer string) string { 133 | now := time.Now().Unix() 134 | payload := []byte(fmt.Sprintf(`{"aud": "%s","exp": %d,"iat": %d,"iss": "%s","tid": "%s"}`, tenant, now+3600, now, issuer, tenant)) 135 | return fmt.Sprintf("header.%s.signature", base64.RawStdEncoding.EncodeToString(payload)) 136 | } 137 | 138 | func GetInstanceDiscoveryBody(host, tenant string) []byte { 139 | authority := fmt.Sprintf("https://%s/%s", host, tenant) 140 | body := fmt.Sprintf(`{"tenant_discovery_endpoint": "%s/v2.0/.well-known/openid-configuration","api-version": "1.1","metadata": [{"preferred_network": "%s","preferred_cache": "%s","aliases": ["%s"]}]}`, 141 | authority, host, host, host, 142 | ) 143 | headers := http.Header{} 144 | headers.Add("Content-Type", "application/json; charset=utf-8") 145 | return []byte(body) 146 | } 147 | 148 | func GetTenantDiscoveryBody(host, tenant string) []byte { 149 | authority := fmt.Sprintf("https://%s/%s", host, tenant) 150 | content := strings.ReplaceAll(`{"token_endpoint": "{authority}/oauth2/v2.0/token", 151 | "token_endpoint_auth_methods_supported": [ 152 | "client_secret_post", 153 | "private_key_jwt", 154 | "client_secret_basic" 155 | ], 156 | "jwks_uri": "{authority}/discovery/v2.0/keys", 157 | "response_modes_supported": [ 158 | "query", 159 | "fragment", 160 | "form_post" 161 | ], 162 | "subject_types_supported": [ 163 | "pairwise" 164 | ], 165 | "id_token_signing_alg_values_supported": [ 166 | "RS256" 167 | ], 168 | "response_types_supported": [ 169 | "code", 170 | "id_token", 171 | "code id_token", 172 | "id_token token" 173 | ], 174 | "scopes_supported": [ 175 | "openid", 176 | "profile", 177 | "email", 178 | "offline_access" 179 | ], 180 | "issuer": "{authority}/v2.0", 181 | "request_uri_parameter_supported": false, 182 | "userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo", 183 | "authorization_endpoint": "{authority}/oauth2/v2.0/authorize", 184 | "device_authorization_endpoint": "{authority}/oauth2/v2.0/devicecode", 185 | "http_logout_supported": true, 186 | "frontchannel_logout_supported": true, 187 | "end_session_endpoint": "{authority}/oauth2/v2.0/logout", 188 | "claims_supported": [ 189 | "sub", 190 | "iss", 191 | "cloud_instance_name", 192 | "cloud_instance_host_name", 193 | "cloud_graph_host_name", 194 | "msgraph_host", 195 | "aud", 196 | "exp", 197 | "iat", 198 | "auth_time", 199 | "acr", 200 | "nonce", 201 | "preferred_username", 202 | "name", 203 | "tid", 204 | "ver", 205 | "at_hash", 206 | "c_hash", 207 | "email" 208 | ], 209 | "kerberos_endpoint": "{authority}/kerberos", 210 | "tenant_region_scope": "NA", 211 | "cloud_instance_name": "microsoftonline.com", 212 | "cloud_graph_host_name": "graph.windows.net", 213 | "msgraph_host": "graph.microsoft.com", 214 | "rbac_url": "https://pas.windows.net" 215 | }`, "{authority}", authority) 216 | return []byte(content) 217 | } 218 | 219 | const Authnschemeformat = "%s-formated" 220 | 221 | type AuthnSchemeTest struct { 222 | } 223 | 224 | func (a *AuthnSchemeTest) TokenRequestParams() map[string]string { 225 | return map[string]string{ 226 | "foo": "bar", 227 | "customHeader": "customHeaderValue", 228 | } 229 | } 230 | 231 | func (a *AuthnSchemeTest) KeyID() string { 232 | return "KeyId" 233 | } 234 | 235 | func (a *AuthnSchemeTest) FormatAccessToken(accessToken string) (string, error) { 236 | return fmt.Sprintf(Authnschemeformat, accessToken), nil 237 | } 238 | 239 | func (a *AuthnSchemeTest) AccessTokenType() string { 240 | return "TokenType" 241 | } 242 | 243 | func NewTestAuthnScheme() authority.AuthenticationScheme { 244 | return &AuthnSchemeTest{} 245 | } 246 | -------------------------------------------------------------------------------- /apps/internal/oauth/fake/fake.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package fake 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | 11 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens" 12 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 13 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust" 14 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs" 15 | ) 16 | 17 | // ResolveEndpoints is a fake implementation of the oauth.resolveEndpointer interface. 18 | type ResolveEndpoints struct { 19 | // Set this to true to have all APIs return an error. 20 | Err bool 21 | 22 | // fake result to return 23 | Endpoints authority.Endpoints 24 | } 25 | 26 | func (f ResolveEndpoints) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) { 27 | if f.Err { 28 | return authority.Endpoints{}, errors.New("error") 29 | } 30 | return f.Endpoints, nil 31 | } 32 | 33 | // AccessTokens is a fake implementation of the oauth.accessTokens interface. 34 | type AccessTokens struct { 35 | // Set this to true to have all APIs return an error. 36 | Err bool 37 | 38 | // Result is for use with FromDeviceCodeResult. On each call it returns 39 | // the next item in this slice. They must be either an error or nil. 40 | Result []error 41 | Next int 42 | 43 | // fake result to return 44 | AccessToken accesstokens.TokenResponse 45 | 46 | // fake result to return 47 | DeviceCode accesstokens.DeviceCodeResult 48 | 49 | // FromRefreshTokenCallback is an optional callback invoked by FromRefreshToken 50 | FromRefreshTokenCallback func(appType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken string) 51 | 52 | // ValidateAssertion is an optional callback for validating an assertion generated by confidential.Client 53 | ValidateAssertion func(string) 54 | } 55 | 56 | func (f *AccessTokens) FromUsernamePassword(ctx context.Context, authParameters authority.AuthParams) (accesstokens.TokenResponse, error) { 57 | if f.Err { 58 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 59 | } 60 | return f.AccessToken, nil 61 | } 62 | func (f *AccessTokens) FromAuthCode(ctx context.Context, req accesstokens.AuthCodeRequest) (accesstokens.TokenResponse, error) { 63 | if f.Err { 64 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 65 | } 66 | return f.AccessToken, nil 67 | } 68 | func (f *AccessTokens) FromRefreshToken(ctx context.Context, appType accesstokens.AppType, authParams authority.AuthParams, cc *accesstokens.Credential, refreshToken string) (accesstokens.TokenResponse, error) { 69 | if f.FromRefreshTokenCallback != nil { 70 | f.FromRefreshTokenCallback(appType, authParams, cc, refreshToken) 71 | } 72 | if f.Err { 73 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 74 | } 75 | return f.AccessToken, nil 76 | } 77 | func (f *AccessTokens) FromClientSecret(ctx context.Context, authParameters authority.AuthParams, clientSecret string) (accesstokens.TokenResponse, error) { 78 | if f.Err { 79 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 80 | } 81 | return f.AccessToken, nil 82 | } 83 | func (f *AccessTokens) FromAssertion(ctx context.Context, authParameters authority.AuthParams, assertion string) (accesstokens.TokenResponse, error) { 84 | if f.Err { 85 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 86 | } 87 | if f.ValidateAssertion != nil { 88 | f.ValidateAssertion(assertion) 89 | } 90 | return f.AccessToken, nil 91 | } 92 | func (f *AccessTokens) FromUserAssertionClientSecret(ctx context.Context, authParameters authority.AuthParams, userAssertion, clientSecret string) (accesstokens.TokenResponse, error) { 93 | if f.Err { 94 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 95 | } 96 | return f.AccessToken, nil 97 | } 98 | func (f *AccessTokens) FromUserAssertionClientCertificate(ctx context.Context, authParameters authority.AuthParams, userAssertion, assertion string) (accesstokens.TokenResponse, error) { 99 | if f.Err { 100 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 101 | } 102 | return f.AccessToken, nil 103 | } 104 | func (f *AccessTokens) DeviceCodeResult(ctx context.Context, authParameters authority.AuthParams) (accesstokens.DeviceCodeResult, error) { 105 | if f.Err { 106 | return accesstokens.DeviceCodeResult{}, fmt.Errorf("error") 107 | } 108 | return f.DeviceCode, nil 109 | } 110 | func (f *AccessTokens) FromDeviceCodeResult(ctx context.Context, authParameters authority.AuthParams, deviceCodeResult accesstokens.DeviceCodeResult) (accesstokens.TokenResponse, error) { 111 | if f.Next < len(f.Result) { 112 | defer func() { f.Next++ }() 113 | v := f.Result[f.Next] 114 | if v == nil { 115 | return f.AccessToken, nil 116 | } 117 | return accesstokens.TokenResponse{}, v 118 | } 119 | panic("AccessTokens.FromDeviceCodeResult() asked for more return values than provided") 120 | } 121 | func (f *AccessTokens) FromSamlGrant(ctx context.Context, authParameters authority.AuthParams, samlGrant wstrust.SamlTokenInfo) (accesstokens.TokenResponse, error) { 122 | if f.Err { 123 | return accesstokens.TokenResponse{}, fmt.Errorf("error") 124 | } 125 | return f.AccessToken, nil 126 | } 127 | 128 | // Authority is a fake implementation of the oauth.fetchAuthority interface. 129 | type Authority struct { 130 | // Set this to true to have all APIs return an error. 131 | Err bool 132 | 133 | // The fake UserRealm to return from the UserRealm() API. 134 | Realm authority.UserRealm 135 | 136 | // fake result to return 137 | InstanceResp authority.InstanceDiscoveryResponse 138 | } 139 | 140 | func (f Authority) UserRealm(ctx context.Context, params authority.AuthParams) (authority.UserRealm, error) { 141 | if f.Err { 142 | return authority.UserRealm{}, errors.New("error") 143 | } 144 | return f.Realm, nil 145 | } 146 | 147 | func (f Authority) AADInstanceDiscovery(ctx context.Context, info authority.Info) (authority.InstanceDiscoveryResponse, error) { 148 | if f.Err { 149 | return authority.InstanceDiscoveryResponse{}, errors.New("error") 150 | } 151 | return f.InstanceResp, nil 152 | } 153 | 154 | // WSTrust is a fake implementation of the oauth.fetchWSTrust interface. 155 | type WSTrust struct { 156 | // Set these to true to have their respective APIs return an error. 157 | GetMexErr, GetSAMLTokenInfoErr bool 158 | 159 | // fake result to return 160 | MexDocument defs.MexDocument 161 | 162 | // fake result to return 163 | SamlTokenInfo wstrust.SamlTokenInfo 164 | } 165 | 166 | func (f WSTrust) Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error) { 167 | if f.GetMexErr { 168 | return defs.MexDocument{}, errors.New("error") 169 | } 170 | return f.MexDocument, nil 171 | } 172 | 173 | func (f WSTrust) SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (wstrust.SamlTokenInfo, error) { 174 | if f.GetSAMLTokenInfoErr { 175 | return wstrust.SamlTokenInfo{}, errors.New("error") 176 | } 177 | return f.SamlTokenInfo, nil 178 | } 179 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/accesstokens/apptype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=AppType"; DO NOT EDIT. 2 | 3 | package accesstokens 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[ATUnknown-0] 12 | _ = x[ATPublic-1] 13 | _ = x[ATConfidential-2] 14 | } 15 | 16 | const _AppType_name = "ATUnknownATPublicATConfidential" 17 | 18 | var _AppType_index = [...]uint8{0, 9, 17, 31} 19 | 20 | func (i AppType) String() string { 21 | if i < 0 || i >= AppType(len(_AppType_index)-1) { 22 | return "AppType(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _AppType_name[_AppType_index[i]:_AppType_index[i+1]] 25 | } 26 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/authority/authorizetype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=AuthorizeType"; DO NOT EDIT. 2 | 3 | package authority 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[ATUnknown-0] 12 | _ = x[ATUsernamePassword-1] 13 | _ = x[ATWindowsIntegrated-2] 14 | _ = x[ATAuthCode-3] 15 | _ = x[ATInteractive-4] 16 | _ = x[ATClientCredentials-5] 17 | _ = x[ATDeviceCode-6] 18 | _ = x[ATRefreshToken-7] 19 | } 20 | 21 | const _AuthorizeType_name = "ATUnknownATUsernamePasswordATWindowsIntegratedATAuthCodeATInteractiveATClientCredentialsATDeviceCodeATRefreshToken" 22 | 23 | var _AuthorizeType_index = [...]uint8{0, 9, 27, 46, 56, 69, 88, 100, 114} 24 | 25 | func (i AuthorizeType) String() string { 26 | if i < 0 || i >= AuthorizeType(len(_AuthorizeType_index)-1) { 27 | return "AuthorizeType(" + strconv.FormatInt(int64(i), 10) + ")" 28 | } 29 | return _AuthorizeType_name[_AuthorizeType_index[i]:_AuthorizeType_index[i+1]] 30 | } 31 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/internal/comm/compress.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package comm 5 | 6 | import ( 7 | "compress/gzip" 8 | "io" 9 | ) 10 | 11 | func gzipDecompress(r io.Reader) io.Reader { 12 | gzipReader, _ := gzip.NewReader(r) 13 | 14 | pipeOut, pipeIn := io.Pipe() 15 | go func() { 16 | // decompression bomb would have to come from Azure services. 17 | // If we want to limit, we should do that in comm.do(). 18 | _, err := io.Copy(pipeIn, gzipReader) //nolint 19 | if err != nil { 20 | // don't need the error. 21 | pipeIn.CloseWithError(err) //nolint 22 | gzipReader.Close() 23 | return 24 | } 25 | if err := gzipReader.Close(); err != nil { 26 | // don't need the error. 27 | pipeIn.CloseWithError(err) //nolint 28 | return 29 | } 30 | pipeIn.Close() 31 | }() 32 | return pipeOut 33 | } 34 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/internal/grant/grant.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Package grant holds types of grants issued by authorization services. 5 | package grant 6 | 7 | const ( 8 | Password = "password" 9 | JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer" 10 | SAMLV1 = "urn:ietf:params:oauth:grant-type:saml1_1-bearer" 11 | SAMLV2 = "urn:ietf:params:oauth:grant-type:saml2-bearer" 12 | DeviceCode = "device_code" 13 | AuthCode = "authorization_code" 14 | RefreshToken = "refresh_token" 15 | ClientCredential = "client_credentials" 16 | ClientAssertion = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" 17 | ) 18 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/ops.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | /* 5 | Package ops provides operations to various backend services using REST clients. 6 | 7 | The REST type provides several clients that can be used to communicate to backends. 8 | Usage is simple: 9 | 10 | rest := ops.New() 11 | 12 | // Creates an authority client and calls the UserRealm() method. 13 | userRealm, err := rest.Authority().UserRealm(ctx, authParameters) 14 | if err != nil { 15 | // Do something 16 | } 17 | */ 18 | package ops 19 | 20 | import ( 21 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens" 22 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 23 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm" 24 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust" 25 | ) 26 | 27 | // HTTPClient represents an HTTP client. 28 | // It's usually an *http.Client from the standard library. 29 | type HTTPClient = comm.HTTPClient 30 | 31 | // REST provides REST clients for communicating with various backends used by MSAL. 32 | type REST struct { 33 | client *comm.Client 34 | } 35 | 36 | // New is the constructor for REST. 37 | func New(httpClient HTTPClient) *REST { 38 | return &REST{client: comm.New(httpClient)} 39 | } 40 | 41 | // Authority returns a client for querying information about various authorities. 42 | func (r *REST) Authority() authority.Client { 43 | return authority.Client{Comm: r.client} 44 | } 45 | 46 | // AccessTokens returns a client that can be used to get various access tokens for 47 | // authorization purposes. 48 | func (r *REST) AccessTokens() accesstokens.Client { 49 | return accesstokens.Client{Comm: r.client} 50 | } 51 | 52 | // WSTrust provides access to various metadata in a WSTrust service. This data can 53 | // be used to gain tokens based on SAML data using the client provided by AccessTokens(). 54 | func (r *REST) WSTrust() wstrust.Client { 55 | return wstrust.Client{Comm: r.client} 56 | } 57 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/wstrust/defs/endpointtype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=endpointType"; DO NOT EDIT. 2 | 3 | package defs 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[etUnknown-0] 12 | _ = x[etUsernamePassword-1] 13 | _ = x[etWindowsTransport-2] 14 | } 15 | 16 | const _endpointType_name = "etUnknownetUsernamePasswordetWindowsTransport" 17 | 18 | var _endpointType_index = [...]uint8{0, 9, 27, 45} 19 | 20 | func (i endpointType) String() string { 21 | if i < 0 || i >= endpointType(len(_endpointType_index)-1) { 22 | return "endpointType(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _endpointType_name[_endpointType_index[i]:_endpointType_index[i+1]] 25 | } 26 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/wstrust/defs/version_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Version"; DO NOT EDIT. 2 | 3 | package defs 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[TrustUnknown-0] 12 | _ = x[Trust2005-1] 13 | _ = x[Trust13-2] 14 | } 15 | 16 | const _Version_name = "TrustUnknownTrust2005Trust13" 17 | 18 | var _Version_index = [...]uint8{0, 12, 21, 28} 19 | 20 | func (i Version) String() string { 21 | if i < 0 || i >= Version(len(_Version_index)-1) { 22 | return "Version(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _Version_name[_Version_index[i]:_Version_index[i+1]] 25 | } 26 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/wstrust/defs/wstrust_endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package defs 5 | 6 | import ( 7 | "encoding/xml" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 12 | uuid "github.com/google/uuid" 13 | ) 14 | 15 | //go:generate stringer -type=Version 16 | 17 | type Version int 18 | 19 | const ( 20 | TrustUnknown Version = iota 21 | Trust2005 22 | Trust13 23 | ) 24 | 25 | // Endpoint represents a WSTrust endpoint. 26 | type Endpoint struct { 27 | // Version is the version of the endpoint. 28 | Version Version 29 | // URL is the URL of the endpoint. 30 | URL string 31 | } 32 | 33 | type wsTrustTokenRequestEnvelope struct { 34 | XMLName xml.Name `xml:"s:Envelope"` 35 | Text string `xml:",chardata"` 36 | S string `xml:"xmlns:s,attr"` 37 | Wsa string `xml:"xmlns:wsa,attr"` 38 | Wsu string `xml:"xmlns:wsu,attr"` 39 | Header struct { 40 | Text string `xml:",chardata"` 41 | Action struct { 42 | Text string `xml:",chardata"` 43 | MustUnderstand string `xml:"s:mustUnderstand,attr"` 44 | } `xml:"wsa:Action"` 45 | MessageID struct { 46 | Text string `xml:",chardata"` 47 | } `xml:"wsa:messageID"` 48 | ReplyTo struct { 49 | Text string `xml:",chardata"` 50 | Address struct { 51 | Text string `xml:",chardata"` 52 | } `xml:"wsa:Address"` 53 | } `xml:"wsa:ReplyTo"` 54 | To struct { 55 | Text string `xml:",chardata"` 56 | MustUnderstand string `xml:"s:mustUnderstand,attr"` 57 | } `xml:"wsa:To"` 58 | Security struct { 59 | Text string `xml:",chardata"` 60 | MustUnderstand string `xml:"s:mustUnderstand,attr"` 61 | Wsse string `xml:"xmlns:wsse,attr"` 62 | Timestamp struct { 63 | Text string `xml:",chardata"` 64 | ID string `xml:"wsu:Id,attr"` 65 | Created struct { 66 | Text string `xml:",chardata"` 67 | } `xml:"wsu:Created"` 68 | Expires struct { 69 | Text string `xml:",chardata"` 70 | } `xml:"wsu:Expires"` 71 | } `xml:"wsu:Timestamp"` 72 | UsernameToken struct { 73 | Text string `xml:",chardata"` 74 | ID string `xml:"wsu:Id,attr"` 75 | Username struct { 76 | Text string `xml:",chardata"` 77 | } `xml:"wsse:Username"` 78 | Password struct { 79 | Text string `xml:",chardata"` 80 | } `xml:"wsse:Password"` 81 | } `xml:"wsse:UsernameToken"` 82 | } `xml:"wsse:Security"` 83 | } `xml:"s:Header"` 84 | Body struct { 85 | Text string `xml:",chardata"` 86 | RequestSecurityToken struct { 87 | Text string `xml:",chardata"` 88 | Wst string `xml:"xmlns:wst,attr"` 89 | AppliesTo struct { 90 | Text string `xml:",chardata"` 91 | Wsp string `xml:"xmlns:wsp,attr"` 92 | EndpointReference struct { 93 | Text string `xml:",chardata"` 94 | Address struct { 95 | Text string `xml:",chardata"` 96 | } `xml:"wsa:Address"` 97 | } `xml:"wsa:EndpointReference"` 98 | } `xml:"wsp:AppliesTo"` 99 | KeyType struct { 100 | Text string `xml:",chardata"` 101 | } `xml:"wst:KeyType"` 102 | RequestType struct { 103 | Text string `xml:",chardata"` 104 | } `xml:"wst:RequestType"` 105 | } `xml:"wst:RequestSecurityToken"` 106 | } `xml:"s:Body"` 107 | } 108 | 109 | func buildTimeString(t time.Time) string { 110 | // Golang time formats are weird: https://stackoverflow.com/questions/20234104/how-to-format-current-time-using-a-yyyymmddhhmmss-format 111 | return t.Format("2006-01-02T15:04:05.000Z") 112 | } 113 | 114 | func (wte *Endpoint) buildTokenRequestMessage(authType authority.AuthorizeType, cloudAudienceURN string, username string, password string) (string, error) { 115 | var soapAction string 116 | var trustNamespace string 117 | var keyType string 118 | var requestType string 119 | 120 | createdTime := time.Now().UTC() 121 | expiresTime := createdTime.Add(10 * time.Minute) 122 | 123 | switch wte.Version { 124 | case Trust2005: 125 | soapAction = trust2005Spec 126 | trustNamespace = "http://schemas.xmlsoap.org/ws/2005/02/trust" 127 | keyType = "http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey" 128 | requestType = "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" 129 | case Trust13: 130 | soapAction = trust13Spec 131 | trustNamespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512" 132 | keyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer" 133 | requestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue" 134 | default: 135 | return "", fmt.Errorf("buildTokenRequestMessage had Version == %q, which is not recognized", wte.Version) 136 | } 137 | 138 | var envelope wsTrustTokenRequestEnvelope 139 | 140 | messageUUID := uuid.New() 141 | 142 | envelope.S = "http://www.w3.org/2003/05/soap-envelope" 143 | envelope.Wsa = "http://www.w3.org/2005/08/addressing" 144 | envelope.Wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 145 | 146 | envelope.Header.Action.MustUnderstand = "1" 147 | envelope.Header.Action.Text = soapAction 148 | envelope.Header.MessageID.Text = "urn:uuid:" + messageUUID.String() 149 | envelope.Header.ReplyTo.Address.Text = "http://www.w3.org/2005/08/addressing/anonymous" 150 | envelope.Header.To.MustUnderstand = "1" 151 | envelope.Header.To.Text = wte.URL 152 | 153 | switch authType { 154 | case authority.ATUnknown: 155 | return "", fmt.Errorf("buildTokenRequestMessage had no authority type(%v)", authType) 156 | case authority.ATUsernamePassword: 157 | endpointUUID := uuid.New() 158 | 159 | var trustID string 160 | if wte.Version == Trust2005 { 161 | trustID = "UnPwSecTok2005-" + endpointUUID.String() 162 | } else { 163 | trustID = "UnPwSecTok13-" + endpointUUID.String() 164 | } 165 | 166 | envelope.Header.Security.MustUnderstand = "1" 167 | envelope.Header.Security.Wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 168 | envelope.Header.Security.Timestamp.ID = "MSATimeStamp" 169 | envelope.Header.Security.Timestamp.Created.Text = buildTimeString(createdTime) 170 | envelope.Header.Security.Timestamp.Expires.Text = buildTimeString(expiresTime) 171 | envelope.Header.Security.UsernameToken.ID = trustID 172 | envelope.Header.Security.UsernameToken.Username.Text = username 173 | envelope.Header.Security.UsernameToken.Password.Text = password 174 | default: 175 | // This is just to note that we don't do anything for other cases. 176 | // We aren't missing anything I know of. 177 | } 178 | 179 | envelope.Body.RequestSecurityToken.Wst = trustNamespace 180 | envelope.Body.RequestSecurityToken.AppliesTo.Wsp = "http://schemas.xmlsoap.org/ws/2004/09/policy" 181 | envelope.Body.RequestSecurityToken.AppliesTo.EndpointReference.Address.Text = cloudAudienceURN 182 | envelope.Body.RequestSecurityToken.KeyType.Text = keyType 183 | envelope.Body.RequestSecurityToken.RequestType.Text = requestType 184 | 185 | output, err := xml.Marshal(envelope) 186 | if err != nil { 187 | return "", err 188 | } 189 | 190 | return string(output), nil 191 | } 192 | 193 | func (wte *Endpoint) BuildTokenRequestMessageWIA(cloudAudienceURN string) (string, error) { 194 | return wte.buildTokenRequestMessage(authority.ATWindowsIntegrated, cloudAudienceURN, "", "") 195 | } 196 | 197 | func (wte *Endpoint) BuildTokenRequestMessageUsernamePassword(cloudAudienceURN string, username string, password string) (string, error) { 198 | return wte.buildTokenRequestMessage(authority.ATUsernamePassword, cloudAudienceURN, username, password) 199 | } 200 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package defs 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | //go:generate stringer -type=endpointType 13 | 14 | type endpointType int 15 | 16 | const ( 17 | etUnknown endpointType = iota 18 | etUsernamePassword 19 | etWindowsTransport 20 | ) 21 | 22 | type wsEndpointData struct { 23 | Version Version 24 | EndpointType endpointType 25 | } 26 | 27 | const trust13Spec string = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" 28 | const trust2005Spec string = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" 29 | 30 | type MexDocument struct { 31 | UsernamePasswordEndpoint Endpoint 32 | WindowsTransportEndpoint Endpoint 33 | policies map[string]endpointType 34 | bindings map[string]wsEndpointData 35 | } 36 | 37 | func updateEndpoint(cached *Endpoint, found Endpoint) { 38 | if cached == nil || cached.Version == TrustUnknown { 39 | *cached = found 40 | return 41 | } 42 | if (*cached).Version == Trust2005 && found.Version == Trust13 { 43 | *cached = found 44 | return 45 | } 46 | } 47 | 48 | // TODO(msal): Someone needs to write tests for everything below. 49 | 50 | // NewFromDef creates a new MexDocument. 51 | func NewFromDef(defs Definitions) (MexDocument, error) { 52 | policies, err := policies(defs) 53 | if err != nil { 54 | return MexDocument{}, err 55 | } 56 | 57 | bindings, err := bindings(defs, policies) 58 | if err != nil { 59 | return MexDocument{}, err 60 | } 61 | 62 | userPass, windows, err := endpoints(defs, bindings) 63 | if err != nil { 64 | return MexDocument{}, err 65 | } 66 | 67 | return MexDocument{ 68 | UsernamePasswordEndpoint: userPass, 69 | WindowsTransportEndpoint: windows, 70 | policies: policies, 71 | bindings: bindings, 72 | }, nil 73 | } 74 | 75 | func policies(defs Definitions) (map[string]endpointType, error) { 76 | policies := make(map[string]endpointType, len(defs.Policy)) 77 | 78 | for _, policy := range defs.Policy { 79 | if policy.ExactlyOne.All.NegotiateAuthentication.XMLName.Local != "" { 80 | if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" { 81 | policies["#"+policy.ID] = etWindowsTransport 82 | } 83 | } 84 | 85 | if policy.ExactlyOne.All.SignedEncryptedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" { 86 | if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" { 87 | policies["#"+policy.ID] = etUsernamePassword 88 | } 89 | } 90 | if policy.ExactlyOne.All.SignedSupportingTokens.Policy.UsernameToken.Policy.WSSUsernameToken10.XMLName.Local != "" { 91 | if policy.ExactlyOne.All.TransportBinding.SP != "" && policy.ID != "" { 92 | policies["#"+policy.ID] = etUsernamePassword 93 | } 94 | } 95 | } 96 | 97 | if len(policies) == 0 { 98 | return policies, errors.New("no policies for mex document") 99 | } 100 | 101 | return policies, nil 102 | } 103 | 104 | func bindings(defs Definitions, policies map[string]endpointType) (map[string]wsEndpointData, error) { 105 | bindings := make(map[string]wsEndpointData, len(defs.Binding)) 106 | 107 | for _, binding := range defs.Binding { 108 | policyName := binding.PolicyReference.URI 109 | transport := binding.Binding.Transport 110 | 111 | if transport == "http://schemas.xmlsoap.org/soap/http" { 112 | if policy, ok := policies[policyName]; ok { 113 | bindingName := binding.Name 114 | specVersion := binding.Operation.Operation.SoapAction 115 | 116 | if specVersion == trust13Spec { 117 | bindings[bindingName] = wsEndpointData{Trust13, policy} 118 | } else if specVersion == trust2005Spec { 119 | bindings[bindingName] = wsEndpointData{Trust2005, policy} 120 | } else { 121 | return nil, errors.New("found unknown spec version in mex document") 122 | } 123 | } 124 | } 125 | } 126 | return bindings, nil 127 | } 128 | 129 | func endpoints(defs Definitions, bindings map[string]wsEndpointData) (userPass, windows Endpoint, err error) { 130 | for _, port := range defs.Service.Port { 131 | bindingName := port.Binding 132 | 133 | index := strings.Index(bindingName, ":") 134 | if index != -1 { 135 | bindingName = bindingName[index+1:] 136 | } 137 | 138 | if binding, ok := bindings[bindingName]; ok { 139 | url := strings.TrimSpace(port.EndpointReference.Address.Text) 140 | if url == "" { 141 | return Endpoint{}, Endpoint{}, fmt.Errorf("MexDocument cannot have blank URL endpoint") 142 | } 143 | if binding.Version == TrustUnknown { 144 | return Endpoint{}, Endpoint{}, fmt.Errorf("endpoint version unknown") 145 | } 146 | endpoint := Endpoint{Version: binding.Version, URL: url} 147 | 148 | switch binding.EndpointType { 149 | case etUsernamePassword: 150 | updateEndpoint(&userPass, endpoint) 151 | case etWindowsTransport: 152 | updateEndpoint(&windows, endpoint) 153 | default: 154 | return Endpoint{}, Endpoint{}, errors.New("found unknown port type in MEX document") 155 | } 156 | } 157 | } 158 | return userPass, windows, nil 159 | } 160 | -------------------------------------------------------------------------------- /apps/internal/oauth/ops/wstrust/wstrust.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | /* 5 | Package wstrust provides a client for communicating with a WSTrust (https://en.wikipedia.org/wiki/WS-Trust#:~:text=WS%2DTrust%20is%20a%20WS,in%20a%20secure%20message%20exchange.) 6 | for the purposes of extracting metadata from the service. This data can be used to acquire 7 | tokens using the accesstokens.Client.GetAccessTokenFromSamlGrant() call. 8 | */ 9 | package wstrust 10 | 11 | import ( 12 | "context" 13 | "errors" 14 | "fmt" 15 | "net/http" 16 | "net/url" 17 | 18 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 19 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant" 20 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs" 21 | ) 22 | 23 | type xmlCaller interface { 24 | XMLCall(ctx context.Context, endpoint string, headers http.Header, qv url.Values, resp interface{}) error 25 | SOAPCall(ctx context.Context, endpoint, action string, headers http.Header, qv url.Values, body string, resp interface{}) error 26 | } 27 | 28 | type SamlTokenInfo struct { 29 | AssertionType string // Should be either constants SAMLV1Grant or SAMLV2Grant. 30 | Assertion string 31 | } 32 | 33 | // Client represents the REST calls to get tokens from token generator backends. 34 | type Client struct { 35 | // Comm provides the HTTP transport client. 36 | Comm xmlCaller 37 | } 38 | 39 | // TODO(msal): This allows me to call Mex without having a real Def file on line 45. 40 | // This would fail because policies() would not find a policy. This is easy enough to 41 | // fix in test data, but.... Definitions is defined with built in structs. That needs 42 | // to be pulled apart and until then I have this hack in. 43 | var newFromDef = defs.NewFromDef 44 | 45 | // Mex provides metadata about a wstrust service. 46 | func (c Client) Mex(ctx context.Context, federationMetadataURL string) (defs.MexDocument, error) { 47 | resp := defs.Definitions{} 48 | err := c.Comm.XMLCall( 49 | ctx, 50 | federationMetadataURL, 51 | http.Header{}, 52 | nil, 53 | &resp, 54 | ) 55 | if err != nil { 56 | return defs.MexDocument{}, err 57 | } 58 | 59 | return newFromDef(resp) 60 | } 61 | 62 | const ( 63 | SoapActionDefault = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue" 64 | 65 | // Note: Commented out because this action is not supported. It was in the original code 66 | // but only used in a switch where it errored. Since there was only one value, a default 67 | // worked better. However, buildTokenRequestMessage() had 2005 support. I'm not actually 68 | // sure what's going on here. It like we have half support. For now this is here just 69 | // for documentation purposes in case we are going to add support. 70 | // 71 | // SoapActionWSTrust2005 = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" 72 | ) 73 | 74 | // SAMLTokenInfo provides SAML information that is used to generate a SAML token. 75 | func (c Client) SAMLTokenInfo(ctx context.Context, authParameters authority.AuthParams, cloudAudienceURN string, endpoint defs.Endpoint) (SamlTokenInfo, error) { 76 | var wsTrustRequestMessage string 77 | var err error 78 | 79 | switch authParameters.AuthorizationType { 80 | case authority.ATWindowsIntegrated: 81 | wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageWIA(cloudAudienceURN) 82 | if err != nil { 83 | return SamlTokenInfo{}, err 84 | } 85 | case authority.ATUsernamePassword: 86 | wsTrustRequestMessage, err = endpoint.BuildTokenRequestMessageUsernamePassword( 87 | cloudAudienceURN, authParameters.Username, authParameters.Password) 88 | if err != nil { 89 | return SamlTokenInfo{}, err 90 | } 91 | default: 92 | return SamlTokenInfo{}, fmt.Errorf("unknown auth type %v", authParameters.AuthorizationType) 93 | } 94 | 95 | var soapAction string 96 | switch endpoint.Version { 97 | case defs.Trust13: 98 | soapAction = SoapActionDefault 99 | case defs.Trust2005: 100 | return SamlTokenInfo{}, errors.New("WS Trust 2005 support is not implemented") 101 | default: 102 | return SamlTokenInfo{}, fmt.Errorf("the SOAP endpoint for a wstrust call had an invalid version: %v", endpoint.Version) 103 | } 104 | 105 | resp := defs.SAMLDefinitions{} 106 | err = c.Comm.SOAPCall(ctx, endpoint.URL, soapAction, http.Header{}, nil, wsTrustRequestMessage, &resp) 107 | if err != nil { 108 | return SamlTokenInfo{}, err 109 | } 110 | 111 | return c.samlAssertion(resp) 112 | } 113 | 114 | const ( 115 | samlv1Assertion = "urn:oasis:names:tc:SAML:1.0:assertion" 116 | samlv2Assertion = "urn:oasis:names:tc:SAML:2.0:assertion" 117 | ) 118 | 119 | func (c Client) samlAssertion(def defs.SAMLDefinitions) (SamlTokenInfo, error) { 120 | for _, tokenResponse := range def.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse { 121 | token := tokenResponse.RequestedSecurityToken 122 | if token.Assertion.XMLName.Local != "" { 123 | assertion := token.AssertionRawXML 124 | 125 | samlVersion := token.Assertion.Saml 126 | switch samlVersion { 127 | case samlv1Assertion: 128 | return SamlTokenInfo{AssertionType: grant.SAMLV1, Assertion: assertion}, nil 129 | case samlv2Assertion: 130 | return SamlTokenInfo{AssertionType: grant.SAMLV2, Assertion: assertion}, nil 131 | } 132 | return SamlTokenInfo{}, fmt.Errorf("couldn't parse SAML assertion, version unknown: %q", samlVersion) 133 | } 134 | } 135 | return SamlTokenInfo{}, errors.New("unknown WS-Trust version") 136 | } 137 | -------------------------------------------------------------------------------- /apps/internal/oauth/resolvers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // TODO(msal): Write some tests. The original code this came from didn't have tests and I'm too 5 | // tired at this point to do it. It, like many other *Manager code I found was broken because 6 | // they didn't have mutex protection. 7 | 8 | package oauth 9 | 10 | import ( 11 | "context" 12 | "errors" 13 | "fmt" 14 | "strings" 15 | "sync" 16 | 17 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops" 18 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 19 | ) 20 | 21 | type cacheEntry struct { 22 | Endpoints authority.Endpoints 23 | ValidForDomainsInList map[string]bool 24 | } 25 | 26 | func createcacheEntry(endpoints authority.Endpoints) cacheEntry { 27 | return cacheEntry{endpoints, map[string]bool{}} 28 | } 29 | 30 | // AuthorityEndpoint retrieves endpoints from an authority for auth and token acquisition. 31 | type authorityEndpoint struct { 32 | rest *ops.REST 33 | 34 | mu sync.Mutex 35 | cache map[string]cacheEntry 36 | } 37 | 38 | // newAuthorityEndpoint is the constructor for AuthorityEndpoint. 39 | func newAuthorityEndpoint(rest *ops.REST) *authorityEndpoint { 40 | m := &authorityEndpoint{rest: rest, cache: map[string]cacheEntry{}} 41 | return m 42 | } 43 | 44 | // ResolveEndpoints gets the authorization and token endpoints and creates an AuthorityEndpoints instance 45 | func (m *authorityEndpoint) ResolveEndpoints(ctx context.Context, authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, error) { 46 | 47 | if endpoints, found := m.cachedEndpoints(authorityInfo, userPrincipalName); found { 48 | return endpoints, nil 49 | } 50 | 51 | endpoint, err := m.openIDConfigurationEndpoint(ctx, authorityInfo) 52 | if err != nil { 53 | return authority.Endpoints{}, err 54 | } 55 | 56 | resp, err := m.rest.Authority().GetTenantDiscoveryResponse(ctx, endpoint) 57 | if err != nil { 58 | return authority.Endpoints{}, err 59 | } 60 | if err := resp.Validate(); err != nil { 61 | return authority.Endpoints{}, fmt.Errorf("ResolveEndpoints(): %w", err) 62 | } 63 | 64 | tenant := authorityInfo.Tenant 65 | 66 | endpoints := authority.NewEndpoints( 67 | strings.Replace(resp.AuthorizationEndpoint, "{tenant}", tenant, -1), 68 | strings.Replace(resp.TokenEndpoint, "{tenant}", tenant, -1), 69 | strings.Replace(resp.Issuer, "{tenant}", tenant, -1), 70 | authorityInfo.Host) 71 | 72 | m.addCachedEndpoints(authorityInfo, userPrincipalName, endpoints) 73 | 74 | return endpoints, nil 75 | } 76 | 77 | // cachedEndpoints returns a the cached endpoints if they exists. If not, we return false. 78 | func (m *authorityEndpoint) cachedEndpoints(authorityInfo authority.Info, userPrincipalName string) (authority.Endpoints, bool) { 79 | m.mu.Lock() 80 | defer m.mu.Unlock() 81 | 82 | if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok { 83 | if authorityInfo.AuthorityType == authority.ADFS { 84 | domain, err := adfsDomainFromUpn(userPrincipalName) 85 | if err == nil { 86 | if _, ok := cacheEntry.ValidForDomainsInList[domain]; ok { 87 | return cacheEntry.Endpoints, true 88 | } 89 | } 90 | } 91 | return cacheEntry.Endpoints, true 92 | } 93 | return authority.Endpoints{}, false 94 | } 95 | 96 | func (m *authorityEndpoint) addCachedEndpoints(authorityInfo authority.Info, userPrincipalName string, endpoints authority.Endpoints) { 97 | m.mu.Lock() 98 | defer m.mu.Unlock() 99 | 100 | updatedCacheEntry := createcacheEntry(endpoints) 101 | 102 | if authorityInfo.AuthorityType == authority.ADFS { 103 | // Since we're here, we've made a call to the backend. We want to ensure we're caching 104 | // the latest values from the server. 105 | if cacheEntry, ok := m.cache[authorityInfo.CanonicalAuthorityURI]; ok { 106 | for k := range cacheEntry.ValidForDomainsInList { 107 | updatedCacheEntry.ValidForDomainsInList[k] = true 108 | } 109 | } 110 | domain, err := adfsDomainFromUpn(userPrincipalName) 111 | if err == nil { 112 | updatedCacheEntry.ValidForDomainsInList[domain] = true 113 | } 114 | } 115 | 116 | m.cache[authorityInfo.CanonicalAuthorityURI] = updatedCacheEntry 117 | } 118 | 119 | func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, authorityInfo authority.Info) (string, error) { 120 | if authorityInfo.AuthorityType == authority.ADFS { 121 | return fmt.Sprintf("https://%s/adfs/.well-known/openid-configuration", authorityInfo.Host), nil 122 | } else if authorityInfo.AuthorityType == authority.DSTS { 123 | return fmt.Sprintf("https://%s/dstsv2/%s/v2.0/.well-known/openid-configuration", authorityInfo.Host, authority.DSTSTenant), nil 124 | 125 | } else if authorityInfo.ValidateAuthority && !authority.TrustedHost(authorityInfo.Host) { 126 | resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo) 127 | if err != nil { 128 | return "", err 129 | } 130 | return resp.TenantDiscoveryEndpoint, nil 131 | } else if authorityInfo.Region != "" { 132 | resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo) 133 | if err != nil { 134 | return "", err 135 | } 136 | return resp.TenantDiscoveryEndpoint, nil 137 | } 138 | 139 | return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil 140 | } 141 | 142 | func adfsDomainFromUpn(userPrincipalName string) (string, error) { 143 | parts := strings.Split(userPrincipalName, "@") 144 | if len(parts) < 2 { 145 | return "", errors.New("no @ present in user principal name") 146 | } 147 | return parts[1], nil 148 | } 149 | -------------------------------------------------------------------------------- /apps/internal/options/options.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package options 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | // CallOption implements an optional argument to a method call. See 12 | // https://blog.devgenius.io/go-call-option-that-can-be-used-with-multiple-methods-6c81734f3dbe 13 | // for an explanation of the usage pattern. 14 | type CallOption interface { 15 | Do(any) error 16 | callOption() 17 | } 18 | 19 | // ApplyOptions applies all the callOptions to options. options must be a pointer to a struct and 20 | // callOptions must be a list of objects that implement CallOption. 21 | func ApplyOptions[O, C any](options O, callOptions []C) error { 22 | for _, o := range callOptions { 23 | if t, ok := any(o).(CallOption); !ok { 24 | return fmt.Errorf("unexpected option type %T", o) 25 | } else if err := t.Do(options); err != nil { 26 | return err 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | // NewCallOption returns a new CallOption whose Do() method calls function "f". 33 | func NewCallOption(f func(any) error) CallOption { 34 | if f == nil { 35 | // This isn't a practical concern because only an MSAL maintainer can get 36 | // us here, by implementing a do-nothing option. But if someone does that, 37 | // the below ensures the method invoked with the option returns an error. 38 | return callOption(func(any) error { 39 | return errors.New("invalid option: missing implementation") 40 | }) 41 | } 42 | return callOption(f) 43 | } 44 | 45 | // callOption is an adapter for a function to a CallOption 46 | type callOption func(any) error 47 | 48 | func (c callOption) Do(a any) error { 49 | return c(a) 50 | } 51 | 52 | func (callOption) callOption() {} 53 | -------------------------------------------------------------------------------- /apps/internal/shared/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package shared 5 | 6 | import ( 7 | "net/http" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | // CacheKeySeparator is used in creating the keys of the cache. 14 | CacheKeySeparator = "-" 15 | ) 16 | 17 | type Account struct { 18 | HomeAccountID string `json:"home_account_id,omitempty"` 19 | Environment string `json:"environment,omitempty"` 20 | Realm string `json:"realm,omitempty"` 21 | LocalAccountID string `json:"local_account_id,omitempty"` 22 | AuthorityType string `json:"authority_type,omitempty"` 23 | PreferredUsername string `json:"username,omitempty"` 24 | GivenName string `json:"given_name,omitempty"` 25 | FamilyName string `json:"family_name,omitempty"` 26 | MiddleName string `json:"middle_name,omitempty"` 27 | Name string `json:"name,omitempty"` 28 | AlternativeID string `json:"alternative_account_id,omitempty"` 29 | RawClientInfo string `json:"client_info,omitempty"` 30 | UserAssertionHash string `json:"user_assertion_hash,omitempty"` 31 | 32 | AdditionalFields map[string]interface{} 33 | } 34 | 35 | // NewAccount creates an account. 36 | func NewAccount(homeAccountID, env, realm, localAccountID, authorityType, username string) Account { 37 | return Account{ 38 | HomeAccountID: homeAccountID, 39 | Environment: env, 40 | Realm: realm, 41 | LocalAccountID: localAccountID, 42 | AuthorityType: authorityType, 43 | PreferredUsername: username, 44 | } 45 | } 46 | 47 | // Key creates the key for storing accounts in the cache. 48 | func (acc Account) Key() string { 49 | key := strings.Join([]string{acc.HomeAccountID, acc.Environment, acc.Realm}, CacheKeySeparator) 50 | return strings.ToLower(key) 51 | } 52 | 53 | // IsZero checks the zero value of account. 54 | func (acc Account) IsZero() bool { 55 | v := reflect.ValueOf(acc) 56 | for i := 0; i < v.NumField(); i++ { 57 | field := v.Field(i) 58 | if !field.IsZero() { 59 | switch field.Kind() { 60 | case reflect.Map, reflect.Slice: 61 | if field.Len() == 0 { 62 | continue 63 | } 64 | } 65 | return false 66 | } 67 | } 68 | return true 69 | } 70 | 71 | // DefaultClient is our default shared HTTP client. 72 | var DefaultClient = &http.Client{} 73 | -------------------------------------------------------------------------------- /apps/internal/shared/shared_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package shared 5 | 6 | import ( 7 | stdJSON "encoding/json" 8 | "testing" 9 | 10 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json" 11 | 12 | "github.com/kylelemons/godebug/pretty" 13 | ) 14 | 15 | var ( 16 | accHID = "hid" 17 | accEnv = "env" 18 | accRealm = "realm" 19 | authType = "MSSTS" 20 | accLid = "lid" 21 | accUser = "user" 22 | ) 23 | 24 | func TestAccountUnmarshal(t *testing.T) { 25 | jsonMap := map[string]interface{}{ 26 | "home_account_id": "hid", 27 | "environment": "env", 28 | "extra": "this_is_extra", 29 | "authority_type": authType, 30 | } 31 | 32 | b, err := stdJSON.Marshal(jsonMap) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | want := Account{ 38 | HomeAccountID: accHID, 39 | Environment: accEnv, 40 | AuthorityType: authType, 41 | AdditionalFields: map[string]interface{}{ 42 | "extra": json.MarshalRaw("this_is_extra"), 43 | }, 44 | } 45 | 46 | got := Account{} 47 | err = json.Unmarshal(b, &got) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | if diff := pretty.Compare(want, got); diff != "" { 53 | t.Errorf("TestAccountUnmarshal: -want/+got:\n%s", diff) 54 | } 55 | } 56 | 57 | func TestAccountKey(t *testing.T) { 58 | acc := &Account{ 59 | HomeAccountID: accHID, 60 | Environment: accEnv, 61 | Realm: accRealm, 62 | } 63 | expectedKey := "hid-env-realm" 64 | actualKey := acc.Key() 65 | if expectedKey != actualKey { 66 | t.Errorf("Actual key %s differs from expected key %s", actualKey, expectedKey) 67 | } 68 | } 69 | 70 | func TestAccountMarshal(t *testing.T) { 71 | acc := Account{ 72 | HomeAccountID: accHID, 73 | Environment: accEnv, 74 | Realm: accRealm, 75 | LocalAccountID: accLid, 76 | AuthorityType: authType, 77 | PreferredUsername: accUser, 78 | AdditionalFields: map[string]interface{}{"extra": "extra"}, 79 | } 80 | 81 | want := map[string]interface{}{ 82 | "home_account_id": "hid", 83 | "environment": "env", 84 | "realm": "realm", 85 | "local_account_id": "lid", 86 | "authority_type": authType, 87 | "username": "user", 88 | "extra": "extra", 89 | } 90 | b, err := json.Marshal(acc) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | got := map[string]interface{}{} 96 | if err := stdJSON.Unmarshal(b, &got); err != nil { 97 | panic(err) 98 | } 99 | 100 | if diff := pretty.Compare(want, got); diff != "" { 101 | t.Errorf("TestAccountMarshal: -want/+got:\n%s", diff) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /apps/internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Package version keeps the version number of the client package. 5 | package version 6 | 7 | // Version is the version of this client package that is communicated to the server. 8 | const Version = "1.4.2" 9 | -------------------------------------------------------------------------------- /apps/managedidentity/azure_ml.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package managedidentity 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | func createAzureMLAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { 13 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, os.Getenv(msiEndpointEnvVar), nil) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | req.Header.Set("secret", os.Getenv(msiSecretEnvVar)) 19 | q := req.URL.Query() 20 | q.Set(apiVersionQueryParameterName, azureMLAPIVersion) 21 | q.Set(resourceQueryParameterName, resource) 22 | q.Set("clientid", os.Getenv("DEFAULT_IDENTITY_CLIENT_ID")) 23 | if cid, ok := id.(UserAssignedClientID); ok { 24 | q.Set("clientid", string(cid)) 25 | } 26 | req.URL.RawQuery = q.Encode() 27 | return req, nil 28 | } 29 | -------------------------------------------------------------------------------- /apps/managedidentity/cloud_shell.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package managedidentity 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | func createCloudShellAuthRequest(ctx context.Context, resource string) (*http.Request, error) { 17 | msiEndpoint := os.Getenv(msiEndpointEnvVar) 18 | msiEndpointParsed, err := url.Parse(msiEndpoint) 19 | if err != nil { 20 | return nil, fmt.Errorf("couldn't parse %q: %s", msiEndpoint, err) 21 | } 22 | 23 | data := url.Values{} 24 | data.Set(resourceQueryParameterName, resource) 25 | msiDataEncoded := data.Encode() 26 | body := io.NopCloser(strings.NewReader(msiDataEncoded)) 27 | 28 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, msiEndpointParsed.String(), body) 29 | if err != nil { 30 | return nil, fmt.Errorf("error creating http request %s", err) 31 | } 32 | 33 | req.Header.Set(metaHTTPHeaderName, "true") 34 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 35 | 36 | return req, nil 37 | } 38 | -------------------------------------------------------------------------------- /apps/managedidentity/servicefabric.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package managedidentity 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | func createServiceFabricAuthRequest(ctx context.Context, resource string) (*http.Request, error) { 13 | identityEndpoint := os.Getenv(identityEndpointEnvVar) 14 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) 15 | if err != nil { 16 | return nil, err 17 | } 18 | req.Header.Set("Accept", "application/json") 19 | req.Header.Set("Secret", os.Getenv(identityHeaderEnvVar)) 20 | q := req.URL.Query() 21 | q.Set("api-version", serviceFabricAPIVersion) 22 | q.Set("resource", resource) 23 | req.URL.RawQuery = q.Encode() 24 | return req, nil 25 | } 26 | -------------------------------------------------------------------------------- /apps/managedidentity/servicefabric_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package managedidentity 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base" 14 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage" 15 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/mock" 16 | ) 17 | 18 | func TestServiceFabricAcquireTokenReturnsTokenSuccess(t *testing.T) { 19 | setEnvVars(t, ServiceFabric) 20 | testCases := []struct { 21 | resource string 22 | miType ID 23 | }{ 24 | {resource: resource, miType: SystemAssigned()}, 25 | {resource: resourceDefaultSuffix, miType: SystemAssigned()}, 26 | } 27 | for _, testCase := range testCases { 28 | t.Run(string(DefaultToIMDS)+"-"+testCase.miType.value(), func(t *testing.T) { 29 | endpoint := imdsDefaultEndpoint 30 | var localUrl *url.URL 31 | var localHeader http.Header 32 | mockClient := mock.NewClient() 33 | responseBody, err := getSuccessfulResponse(resource, true) 34 | if err != nil { 35 | t.Fatalf(errorFormingJsonResponse, err.Error()) 36 | } 37 | 38 | mockClient.AppendResponse(mock.WithHTTPStatusCode(http.StatusOK), mock.WithBody(responseBody), mock.WithCallback(func(r *http.Request) { 39 | localUrl = r.URL 40 | localHeader = r.Header 41 | })) 42 | // resetting cache 43 | before := cacheManager 44 | defer func() { cacheManager = before }() 45 | cacheManager = storage.New(nil) 46 | 47 | client, err := New(testCase.miType, WithHTTPClient(mockClient)) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | result, err := client.AcquireToken(context.Background(), testCase.resource) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | if localUrl == nil || !strings.HasPrefix(localUrl.String(), "http://localhost:40342/metadata/identity/oauth2/token") { 56 | t.Fatalf("url request is not on %s got %s", endpoint, localUrl) 57 | } 58 | query := localUrl.Query() 59 | 60 | if got := query.Get(apiVersionQueryParameterName); got != serviceFabricAPIVersion { 61 | t.Fatalf("api-version not on %s got %s", serviceFabricAPIVersion, got) 62 | } 63 | if query.Get(resourceQueryParameterName) != strings.TrimSuffix(testCase.resource, "/.default") { 64 | t.Fatal("suffix /.default was not removed.") 65 | } 66 | if localHeader.Get("Accept") != "application/json" { 67 | t.Fatalf("expected Accept header to be application/json, got %s", localHeader.Get("Accept")) 68 | } 69 | if localHeader.Get("Secret") != "secret" { 70 | t.Fatalf("expected secret to be secret, got %s", query.Get("Secret")) 71 | } 72 | if result.Metadata.TokenSource != base.TokenSourceIdentityProvider { 73 | t.Fatalf("expected IndenityProvider tokensource, got %d", result.Metadata.TokenSource) 74 | } 75 | if result.AccessToken != token { 76 | t.Fatalf("wanted %q, got %q", token, result.AccessToken) 77 | } 78 | result, err = client.AcquireToken(context.Background(), testCase.resource) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | if result.Metadata.TokenSource != base.TokenSourceCache { 83 | t.Fatalf("wanted cache token source, got %d", result.Metadata.TokenSource) 84 | } 85 | secondFakeClient, err := New(testCase.miType, WithHTTPClient(mockClient)) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | result, err = secondFakeClient.AcquireToken(context.Background(), testCase.resource) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | if result.Metadata.TokenSource != base.TokenSourceCache { 94 | t.Fatalf("cache result wanted cache token source, got %d", result.Metadata.TokenSource) 95 | } 96 | }) 97 | } 98 | } 99 | func TestServiceFabricErrors(t *testing.T) { 100 | setEnvVars(t, ServiceFabric) 101 | mockClient := mock.NewClient() 102 | 103 | for _, testCase := range []ID{ 104 | UserAssignedObjectID("ObjectId"), 105 | UserAssignedResourceID("resourceid"), 106 | UserAssignedClientID("ClientID")} { 107 | _, err := New(testCase, WithHTTPClient(mockClient)) 108 | if err == nil { 109 | t.Fatal("expected error: Service Fabric API doesn't support specifying a user-assigned identity. The identity is determined by cluster resource configuration. See https://aka.ms/servicefabricmi") 110 | } 111 | if err.Error() != "Service Fabric API doesn't support specifying a user-assigned identity. The identity is determined by cluster resource configuration. See https://aka.ms/servicefabricmi" { 112 | t.Fatalf("expected error: Service Fabric API doesn't support specifying a user-assigned identity. The identity is determined by cluster resource configuration. See https://aka.ms/servicefabricmi, got error: %q", err) 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /apps/public/example_test.go: -------------------------------------------------------------------------------- 1 | package public_test 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" 7 | ) 8 | 9 | // This example demonstrates the general pattern for authenticating with MSAL Go: 10 | // - create a client (only necessary at application start--it's best to reuse client instances) 11 | // - call AcquireTokenSilent() to search for a cached access token 12 | // - if the cache misses, acquire a new token 13 | func Example() { 14 | client, err := public.New("client_id", public.WithAuthority("https://login.microsoftonline.com/your_tenant")) 15 | if err != nil { 16 | // TODO: handle error 17 | } 18 | 19 | var result public.AuthResult 20 | scopes := []string{"scope"} 21 | 22 | // If your application previously authenticated a user, call AcquireTokenSilent with that user's account 23 | // to use cached authentication data. This example shows choosing an account from the cache, however this 24 | // isn't always necessary because the AuthResult returned by authentication methods includes user account 25 | // information. 26 | accounts, err := client.Accounts(context.TODO()) 27 | if err != nil { 28 | // TODO: handle error 29 | } 30 | if len(accounts) > 0 { 31 | // There may be more accounts; here we assume the first one is wanted. 32 | // AcquireTokenSilent returns a non-nil error when it can't provide a token. 33 | result, err = client.AcquireTokenSilent(context.TODO(), scopes, public.WithSilentAccount(accounts[0])) 34 | } 35 | if err != nil || len(accounts) == 0 { 36 | // cache miss, authenticate a user with another AcquireToken* method 37 | result, err = client.AcquireTokenInteractive(context.TODO(), scopes) 38 | if err != nil { 39 | // TODO: handle error 40 | } 41 | } 42 | 43 | // TODO: save the authenticated user's account, use the access token 44 | _ = result.Account 45 | _ = result.AccessToken 46 | } 47 | -------------------------------------------------------------------------------- /apps/testdata/test-cert-chain-reverse.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFGTCCAwGgAwIBAgIUBpOlpNN/cgasvozVw6mfa04+ZC0wDQYJKoZIhvcNAQEL 3 | BQAwOzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA3h6eTEMMAoGA1UECwwDYWJjMRAw 4 | DgYDVQQDDAdST09ULUNOMCAXDTIwMDgyMTE3MTAyNVoYDzMzODkwODA0MTcxMDI1 5 | WjA+MQswCQYDVQQGEwJVUzEMMAoGA1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEzAR 6 | BgNVBAMMCklOVEVSSU0tQ04wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC 7 | AQCr+Tblr4DhX3Xahbei00OJnUgRw6FMsnyROZ170Lx0YNcOrRJ9PuaOZiYXY2Hm 8 | t71o/PZjMtmiYMIxFaiMnql/dCca777l+uBmlwFOR8bquBWiLStmPpvf7Kh5GZNw 9 | XvLGAhk/oxG0O9Pa3OfrlD5vrn/UEGJBu0C+c6ZSLyRk8RjAh8ZbUvnDhhQw3PoK 10 | MQSmFK8BN8X34elu7kq0j7nS0D6Mt7eS40oYeHEaQDdBGl8f7rcqC3RjJ/b/F9wA 11 | +CsKaps6TvpxE7ln9Y3+0yscgeRbyHW0zem6U7MMvVnK/znuNY90Wmajbea7SUj6 12 | nGZpLGS1TqS4H5rn9U1N1WCSyFukTpAQLCPQHeUrSiHKa9Ye5KuC6u2ZXgy0qpGj 13 | nMLu+7746wemi7jN06yZjEmDVneMNCxjLYs4ZhuhiTEItlZpR0VBugNbKo2mJw2U 14 | UesizB3AzQkqGOKp70y74yC+ykLkR5vRNyY3MENJ+W83U1haS7C1rhqFV4eXflVe 15 | EHl8tj7p4KrfhSPr0Rd12UIWDXkYUpCAPlDMdEa9+SDAyuSnkN4P1fAeuzG01jeJ 16 | bnsrWgs3gH3KaGBcPTV4tOTavilGNYDvHZbN9XpYZoZQoPrDZc61M5Ol/cxBahkO 17 | n4aDyhpx5hHnSs7VQuHnjeMUxt3J5HqrXPvaf6uPYNT8KQIDAQABoxAwDjAMBgNV 18 | HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCHCxFqJwfVMI9kMvwlj+sxd4Q5 19 | KuyWxlXRfzpYZ/6JCUq7VBceRVJ87KytMNCyq61rd3Jhb8ssoMCENB68HYhIFUGz 20 | GR92AAc6LTh2Y3vQAg640Cz2vLCGnqnlbIslYV6fzxYqgSopR5wJ4D/kJ9w7NSrC 21 | paN6bS8Olv//tN6RSnvEMJZdXFA40xFin6qT8Op3nrysEE7Z84wPG9Wj2DXskX6v 22 | bZenCEgl1/Ezif5IEgJcYdRkXtYPp6JNbVV+KjDTIMEaUVMpGMGefrt22E+4nSa3 23 | qFvcbzYEKeANe9IAxdPzeWiQ2U90PqWFYCA9sOVsrlSwrup+yYXl0yhTxKY67NCX 24 | gyVtZRnzawv0AVFsfCOT4V0wJSuUz4BV6sH7kl2C7FW3zqYVdFEDigbUNsEEh/jF 25 | 3JiAtgNbpJ8TtiCFrCI4g9Jepa3polVPzDD8mLtkWWnfSBN/28cxa2jiUlfQxB39 26 | kyqu4rWbm01lyucJxVgJzH0SGyEM5OvF/OIOU3Q7UIXEcZSX3m4Xo59+v6ZNDwKL 27 | PcFDNK+PL3WNYfdexQCSAbLm1gkUrVIqvidpCSSVv5oWwTM5m7rbA16Hlu4Ea2ep 28 | Pl7I9YXXXnIEFqLYZDnCJglcXmlt6OjI8D3w0TRWHb6bFqubDP417sJDX1S6udN5 29 | wOnOIqg0ZZcqfvpxXA== 30 | -----END CERTIFICATE----- 31 | -----BEGIN CERTIFICATE----- 32 | MIID7zCCAdcCAQEwDQYJKoZIhvcNAQEFBQAwPjELMAkGA1UEBhMCVVMxDDAKBgNV 33 | BAoMA3h5ejEMMAoGA1UECwwDYWJjMRMwEQYDVQQDDApJTlRFUklNLUNOMCAXDTIw 34 | MDgyMTE3MTA0M1oYDzMzODkwODA0MTcxMDQzWjA7MQswCQYDVQQGEwJVUzEMMAoG 35 | A1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEDAOBgNVBAMMB1VTRVItQ04wggEiMA0G 36 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6eQYdbIFhsinob3t3AV4yEH/tz/LV 37 | I+UAGLpxQnqGnuAV5GY3CXiAO8GZjx7y3oA1DGfe+/cc6n9BmYWXsKvxpKO8PQkB 38 | PYIFtD878uDNv7kVoZG8EVsEngBxd4efMniKWwKtMle0hZ+jj3u4Ad49DsXcC0L2 39 | 8uV/eQ6hzsQiR0nTQJ/4QqNNtThSGAFSr7Oo8xzxBNTJhe+BvwDE8JMkCS0v22JW 40 | my2GYrRKw4RlSKxwv9QZr83gSicKSUPUACBYfJ7RuXSQOHOMlIcC4oGtDrMshGzr 41 | 704Ho+DiByYf5G6nkfZ1I7T039gEKKIllNKWqhyQHejKba3nP163ZKI3AgMBAAEw 42 | DQYJKoZIhvcNAQEFBQADggIBADfitSfjlYa2inBKlpWN8VT0DPm5uw8EHuwLymCM 43 | WYrQMCuQVE2xYoqCSmXj6KLFt8ycgxHsthdkAzXxDhawaKjz2UFp6nszmUA4xfvS 44 | mxLSajwzK/KMBkjdFL7TM+TTBJ1bleDbmoJvDiUeQwisbb1Uh8b3v/jpBwoiamm8 45 | Y4Ca5A15SeBUvAt0/Mc4XJfZ/Ts+LBAPevI9ZyU7C5JZky1q41KPklEHfFZKQRfP 46 | cTyTYYvlPoq57C8XPDs6r50EV3B6Z8MN21OB6MVGi8BOY/c7a2h1ZOhxNyBnJuQX 47 | w4meJthoKcHUnAs8YCrEoQKayMqPH0Vdhaii/gx4jAgh4PNyIZz5cAst+ybPtQj4 48 | i7LFEWjxis+NLQMHhyE4fIGIkEjzU0uGDugifheIwKALqYEgMDrcoolwvGMdPxGo 49 | Qps7tkad5vZV9d9+tTbI+DMB16Y51S04/u1dGFz3jSrDVF08PznJc99VB69OReiC 50 | K17n8Xyox/VAaYsRFbOAJpLRWwcnotDpFQbgiLrmXxNOoiWPNbQsQzaQx7cR9okQ 51 | v5RTpFAkrdjadhMsXFFiQh+axlaGD368ZGAj5ZoyOiXkV88tNCtyP/RDgW5ftQQ7 52 | fdv05bNXhDfLgEgQvVSDfClDL1hKukLmLQS3ILfB4FlM/XmE+FW/qgo9aSx2XIbx 53 | E4ie 54 | -----END CERTIFICATE----- 55 | -----BEGIN RSA PRIVATE KEY----- 56 | MIIEowIBAAKCAQEAunkGHWyBYbIp6G97dwFeMhB/7c/y1SPlABi6cUJ6hp7gFeRm 57 | Nwl4gDvBmY8e8t6ANQxn3vv3HOp/QZmFl7Cr8aSjvD0JAT2CBbQ/O/Lgzb+5FaGR 58 | vBFbBJ4AcXeHnzJ4ilsCrTJXtIWfo497uAHePQ7F3AtC9vLlf3kOoc7EIkdJ00Cf 59 | +EKjTbU4UhgBUq+zqPMc8QTUyYXvgb8AxPCTJAktL9tiVpsthmK0SsOEZUiscL/U 60 | Ga/N4EonCklD1AAgWHye0bl0kDhzjJSHAuKBrQ6zLIRs6+9OB6Pg4gcmH+Rup5H2 61 | dSO09N/YBCiiJZTSlqockB3oym2t5z9et2SiNwIDAQABAoIBAQCKzivPG0X0AztO 62 | 2i19mHcVrVKNI44POnjsaXvfcyzhqMIFic7MiTA5xEGInRDcmOO2mVV4lvaLf8La 63 | gfz/vXNAnN2E8aoSUkbHGDU52sGcZmrPv0VMSV8HQNXzoJZD2r3/v19urVq79fuv 64 | NM9TWZCkwqpl8bwXNxe+m85YhCFboY9G543qmuXzKAQLoSupT0e4eIo2IGp7eJYK 65 | 5J/wtlEumUdhsKo1ajLojDgsgPKfrCyvsmO+bj1dRKGXVLO2SL2pFVCjjHF4SP3q 66 | 1WX39beu61Zu+kGthDgj5muHgH06FtnWoHLIUrRmYpM+ezCxQHdRWz7AYjheeE7q 67 | QqJv1PqBAoGBAOlb/gzsps+rInE+LQoEzVj8osILI4NxIpNc6+iG81dEi+zQABX/ 68 | bHV6hXGGceozVcX4B+V7f08PlZIAgM3IDqfy0fH2pwEQahJ8a3MwzCgR66RxYlkX 69 | E8czkoz0pcHW58FnLLlWXpHRALTtqoPP5LnWs0SmoNvcHZ9yjJ6tvpRlAoGBAMyQ 70 | fytsyla1ujO0l/kuLFG7gndeOc96SutH3V17lZ1pN0efHyk2aglOnl6YsdPKLZvZ 71 | 3ghj01HV0Q0f//xpftduuA7gdgDzSG1irXsxEidfVxX7RsPxX6cx8dhYnuk5rz5E 72 | XyTko7zTpr+A4XMnq6+JNSSCIE+CVYcYf/hyemxrAoGAeC9py4xCaWgxR/OGzMcm 73 | X3NV++wysSqebRkJYuvF/icOjbuen7W6TVL50Ts2BjHENj6FCpqtObHEDbr2m4Uy 74 | jysPF7g50OF8T+MGkAAM1YJNQ5cl2M564DhefPwvNoMRP1l8/kNOV3k2DPjuvg5f 75 | NZsvHudWp4VZOFqNs9e19MUCgYAjewCDoKfrqDN2mmEtmAOZ3YMAfzhZsyVhb6KG 76 | f1Pw7HnpE0FNXaHAoYE4eRWG3W9Rs9Ud8WqKrCJJO36j4gxdA1grRGVTPt8WEeJz 77 | FozGhXPOXTnl7GyhzDjdRGmznAy4KRWziXCY5MDsQEdaOMw/cvXjsio2gC2jc+1m 78 | QzzWpwKBgHzszJ5s6vcWElox4Yc1elQ8xniPpo3RtfXZOLX8xA4eR9yQawah1zd6 79 | ChfeYbHVfq007s+RWGTb+KYQ6ic9nkW464qmVxHGBatUo9+MR4Gk8blANoAfHxdV 80 | g6JNgT2kIGu9IEwoD6XQldC/v24bvFSesyGRHNdI4mUG+hhU4aNw 81 | -----END RSA PRIVATE KEY----- 82 | -------------------------------------------------------------------------------- /apps/testdata/test-cert-chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAunkGHWyBYbIp6G97dwFeMhB/7c/y1SPlABi6cUJ6hp7gFeRm 3 | Nwl4gDvBmY8e8t6ANQxn3vv3HOp/QZmFl7Cr8aSjvD0JAT2CBbQ/O/Lgzb+5FaGR 4 | vBFbBJ4AcXeHnzJ4ilsCrTJXtIWfo497uAHePQ7F3AtC9vLlf3kOoc7EIkdJ00Cf 5 | +EKjTbU4UhgBUq+zqPMc8QTUyYXvgb8AxPCTJAktL9tiVpsthmK0SsOEZUiscL/U 6 | Ga/N4EonCklD1AAgWHye0bl0kDhzjJSHAuKBrQ6zLIRs6+9OB6Pg4gcmH+Rup5H2 7 | dSO09N/YBCiiJZTSlqockB3oym2t5z9et2SiNwIDAQABAoIBAQCKzivPG0X0AztO 8 | 2i19mHcVrVKNI44POnjsaXvfcyzhqMIFic7MiTA5xEGInRDcmOO2mVV4lvaLf8La 9 | gfz/vXNAnN2E8aoSUkbHGDU52sGcZmrPv0VMSV8HQNXzoJZD2r3/v19urVq79fuv 10 | NM9TWZCkwqpl8bwXNxe+m85YhCFboY9G543qmuXzKAQLoSupT0e4eIo2IGp7eJYK 11 | 5J/wtlEumUdhsKo1ajLojDgsgPKfrCyvsmO+bj1dRKGXVLO2SL2pFVCjjHF4SP3q 12 | 1WX39beu61Zu+kGthDgj5muHgH06FtnWoHLIUrRmYpM+ezCxQHdRWz7AYjheeE7q 13 | QqJv1PqBAoGBAOlb/gzsps+rInE+LQoEzVj8osILI4NxIpNc6+iG81dEi+zQABX/ 14 | bHV6hXGGceozVcX4B+V7f08PlZIAgM3IDqfy0fH2pwEQahJ8a3MwzCgR66RxYlkX 15 | E8czkoz0pcHW58FnLLlWXpHRALTtqoPP5LnWs0SmoNvcHZ9yjJ6tvpRlAoGBAMyQ 16 | fytsyla1ujO0l/kuLFG7gndeOc96SutH3V17lZ1pN0efHyk2aglOnl6YsdPKLZvZ 17 | 3ghj01HV0Q0f//xpftduuA7gdgDzSG1irXsxEidfVxX7RsPxX6cx8dhYnuk5rz5E 18 | XyTko7zTpr+A4XMnq6+JNSSCIE+CVYcYf/hyemxrAoGAeC9py4xCaWgxR/OGzMcm 19 | X3NV++wysSqebRkJYuvF/icOjbuen7W6TVL50Ts2BjHENj6FCpqtObHEDbr2m4Uy 20 | jysPF7g50OF8T+MGkAAM1YJNQ5cl2M564DhefPwvNoMRP1l8/kNOV3k2DPjuvg5f 21 | NZsvHudWp4VZOFqNs9e19MUCgYAjewCDoKfrqDN2mmEtmAOZ3YMAfzhZsyVhb6KG 22 | f1Pw7HnpE0FNXaHAoYE4eRWG3W9Rs9Ud8WqKrCJJO36j4gxdA1grRGVTPt8WEeJz 23 | FozGhXPOXTnl7GyhzDjdRGmznAy4KRWziXCY5MDsQEdaOMw/cvXjsio2gC2jc+1m 24 | QzzWpwKBgHzszJ5s6vcWElox4Yc1elQ8xniPpo3RtfXZOLX8xA4eR9yQawah1zd6 25 | ChfeYbHVfq007s+RWGTb+KYQ6ic9nkW464qmVxHGBatUo9+MR4Gk8blANoAfHxdV 26 | g6JNgT2kIGu9IEwoD6XQldC/v24bvFSesyGRHNdI4mUG+hhU4aNw 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIID7zCCAdcCAQEwDQYJKoZIhvcNAQEFBQAwPjELMAkGA1UEBhMCVVMxDDAKBgNV 30 | BAoMA3h5ejEMMAoGA1UECwwDYWJjMRMwEQYDVQQDDApJTlRFUklNLUNOMCAXDTIw 31 | MDgyMTE3MTA0M1oYDzMzODkwODA0MTcxMDQzWjA7MQswCQYDVQQGEwJVUzEMMAoG 32 | A1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEDAOBgNVBAMMB1VTRVItQ04wggEiMA0G 33 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6eQYdbIFhsinob3t3AV4yEH/tz/LV 34 | I+UAGLpxQnqGnuAV5GY3CXiAO8GZjx7y3oA1DGfe+/cc6n9BmYWXsKvxpKO8PQkB 35 | PYIFtD878uDNv7kVoZG8EVsEngBxd4efMniKWwKtMle0hZ+jj3u4Ad49DsXcC0L2 36 | 8uV/eQ6hzsQiR0nTQJ/4QqNNtThSGAFSr7Oo8xzxBNTJhe+BvwDE8JMkCS0v22JW 37 | my2GYrRKw4RlSKxwv9QZr83gSicKSUPUACBYfJ7RuXSQOHOMlIcC4oGtDrMshGzr 38 | 704Ho+DiByYf5G6nkfZ1I7T039gEKKIllNKWqhyQHejKba3nP163ZKI3AgMBAAEw 39 | DQYJKoZIhvcNAQEFBQADggIBADfitSfjlYa2inBKlpWN8VT0DPm5uw8EHuwLymCM 40 | WYrQMCuQVE2xYoqCSmXj6KLFt8ycgxHsthdkAzXxDhawaKjz2UFp6nszmUA4xfvS 41 | mxLSajwzK/KMBkjdFL7TM+TTBJ1bleDbmoJvDiUeQwisbb1Uh8b3v/jpBwoiamm8 42 | Y4Ca5A15SeBUvAt0/Mc4XJfZ/Ts+LBAPevI9ZyU7C5JZky1q41KPklEHfFZKQRfP 43 | cTyTYYvlPoq57C8XPDs6r50EV3B6Z8MN21OB6MVGi8BOY/c7a2h1ZOhxNyBnJuQX 44 | w4meJthoKcHUnAs8YCrEoQKayMqPH0Vdhaii/gx4jAgh4PNyIZz5cAst+ybPtQj4 45 | i7LFEWjxis+NLQMHhyE4fIGIkEjzU0uGDugifheIwKALqYEgMDrcoolwvGMdPxGo 46 | Qps7tkad5vZV9d9+tTbI+DMB16Y51S04/u1dGFz3jSrDVF08PznJc99VB69OReiC 47 | K17n8Xyox/VAaYsRFbOAJpLRWwcnotDpFQbgiLrmXxNOoiWPNbQsQzaQx7cR9okQ 48 | v5RTpFAkrdjadhMsXFFiQh+axlaGD368ZGAj5ZoyOiXkV88tNCtyP/RDgW5ftQQ7 49 | fdv05bNXhDfLgEgQvVSDfClDL1hKukLmLQS3ILfB4FlM/XmE+FW/qgo9aSx2XIbx 50 | E4ie 51 | -----END CERTIFICATE----- 52 | -----BEGIN CERTIFICATE----- 53 | MIIFGTCCAwGgAwIBAgIUBpOlpNN/cgasvozVw6mfa04+ZC0wDQYJKoZIhvcNAQEL 54 | BQAwOzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA3h6eTEMMAoGA1UECwwDYWJjMRAw 55 | DgYDVQQDDAdST09ULUNOMCAXDTIwMDgyMTE3MTAyNVoYDzMzODkwODA0MTcxMDI1 56 | WjA+MQswCQYDVQQGEwJVUzEMMAoGA1UECgwDeHl6MQwwCgYDVQQLDANhYmMxEzAR 57 | BgNVBAMMCklOVEVSSU0tQ04wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC 58 | AQCr+Tblr4DhX3Xahbei00OJnUgRw6FMsnyROZ170Lx0YNcOrRJ9PuaOZiYXY2Hm 59 | t71o/PZjMtmiYMIxFaiMnql/dCca777l+uBmlwFOR8bquBWiLStmPpvf7Kh5GZNw 60 | XvLGAhk/oxG0O9Pa3OfrlD5vrn/UEGJBu0C+c6ZSLyRk8RjAh8ZbUvnDhhQw3PoK 61 | MQSmFK8BN8X34elu7kq0j7nS0D6Mt7eS40oYeHEaQDdBGl8f7rcqC3RjJ/b/F9wA 62 | +CsKaps6TvpxE7ln9Y3+0yscgeRbyHW0zem6U7MMvVnK/znuNY90Wmajbea7SUj6 63 | nGZpLGS1TqS4H5rn9U1N1WCSyFukTpAQLCPQHeUrSiHKa9Ye5KuC6u2ZXgy0qpGj 64 | nMLu+7746wemi7jN06yZjEmDVneMNCxjLYs4ZhuhiTEItlZpR0VBugNbKo2mJw2U 65 | UesizB3AzQkqGOKp70y74yC+ykLkR5vRNyY3MENJ+W83U1haS7C1rhqFV4eXflVe 66 | EHl8tj7p4KrfhSPr0Rd12UIWDXkYUpCAPlDMdEa9+SDAyuSnkN4P1fAeuzG01jeJ 67 | bnsrWgs3gH3KaGBcPTV4tOTavilGNYDvHZbN9XpYZoZQoPrDZc61M5Ol/cxBahkO 68 | n4aDyhpx5hHnSs7VQuHnjeMUxt3J5HqrXPvaf6uPYNT8KQIDAQABoxAwDjAMBgNV 69 | HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCHCxFqJwfVMI9kMvwlj+sxd4Q5 70 | KuyWxlXRfzpYZ/6JCUq7VBceRVJ87KytMNCyq61rd3Jhb8ssoMCENB68HYhIFUGz 71 | GR92AAc6LTh2Y3vQAg640Cz2vLCGnqnlbIslYV6fzxYqgSopR5wJ4D/kJ9w7NSrC 72 | paN6bS8Olv//tN6RSnvEMJZdXFA40xFin6qT8Op3nrysEE7Z84wPG9Wj2DXskX6v 73 | bZenCEgl1/Ezif5IEgJcYdRkXtYPp6JNbVV+KjDTIMEaUVMpGMGefrt22E+4nSa3 74 | qFvcbzYEKeANe9IAxdPzeWiQ2U90PqWFYCA9sOVsrlSwrup+yYXl0yhTxKY67NCX 75 | gyVtZRnzawv0AVFsfCOT4V0wJSuUz4BV6sH7kl2C7FW3zqYVdFEDigbUNsEEh/jF 76 | 3JiAtgNbpJ8TtiCFrCI4g9Jepa3polVPzDD8mLtkWWnfSBN/28cxa2jiUlfQxB39 77 | kyqu4rWbm01lyucJxVgJzH0SGyEM5OvF/OIOU3Q7UIXEcZSX3m4Xo59+v6ZNDwKL 78 | PcFDNK+PL3WNYfdexQCSAbLm1gkUrVIqvidpCSSVv5oWwTM5m7rbA16Hlu4Ea2ep 79 | Pl7I9YXXXnIEFqLYZDnCJglcXmlt6OjI8D3w0TRWHb6bFqubDP417sJDX1S6udN5 80 | wOnOIqg0ZZcqfvpxXA== 81 | -----END CERTIFICATE----- 82 | -------------------------------------------------------------------------------- /apps/testdata/test-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICljCCAX4CCQDNgteZ+lJH4zANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 3 | czAeFw0yMTAxMDQyMzQzNDVaFw0yMTAyMDMyMzQzNDVaMA0xCzAJBgNVBAYTAnVz 4 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1r58wq7JQxM12viLNbdG 5 | fFizeVQwWRwrx/4CH3kU8jjGovbhkvC/uLWqVGchgATThhGkvNrA92WvdkVwsZMk 6 | Qf7ZnTA7kemo4VFtgo5XCGEej9gOTW13Evdc/0Flip+RXl3h3Q6BbbB9IFE0c6cS 7 | 3i/v/t8KGpVYQHQzBwTcYehM6eDO8ZjUyUUcJOMXdMCctamig7fMGlziKFahn4dX 8 | JoiiK4oNKE9okXIAXCTbVkAxxH0hD+5XH1nn5LJnHe0e5DflI3YIiPgmRL5uC89K 9 | XqmYCKWrq5z2D5k+5fQLmbOcxErBcFCh8hA+Xu0RLT4BHPEgc6iVIqxL4CZi/cke 10 | uwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAAyDbm0Fda0/vY6ZVDML2IbGWbro1w 11 | nWYNw6wclNU6sx1oeG/k/y2ni7NImPpbFN+594WS6rYHgFdROfeuNgGnjgQCJogk 12 | +8ouf1R6vFMUAScWeSaFnZmBEgwofWsnIcUKkbDIXbpRhMrkNEcY09VgjmCKhspQ 13 | iX2bJQTj49XBac9tBaJJYDZ4HgkO4nU7QeEPpvwlELZFoZZXtd3fan+VUyFS2a9n 14 | gkAMDYoQPGN4tyGFabWws/GlMxelWvqUzpQKmeRPVz+cij75l8eKThEiu0zbjOTD 15 | Gq81BcY61SPqN02zoPCtqZ/zU6HhaL3x7zUuzhLhNoh83A43UVYEoOOf 16 | -----END CERTIFICATE----- 17 | -----BEGIN PRIVATE KEY----- 18 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDWvnzCrslDEzXa 19 | +Is1t0Z8WLN5VDBZHCvH/gIfeRTyOMai9uGS8L+4tapUZyGABNOGEaS82sD3Za92 20 | RXCxkyRB/tmdMDuR6ajhUW2CjlcIYR6P2A5NbXcS91z/QWWKn5FeXeHdDoFtsH0g 21 | UTRzpxLeL+/+3woalVhAdDMHBNxh6Ezp4M7xmNTJRRwk4xd0wJy1qaKDt8waXOIo 22 | VqGfh1cmiKIrig0oT2iRcgBcJNtWQDHEfSEP7lcfWefksmcd7R7kN+UjdgiI+CZE 23 | vm4Lz0peqZgIpaurnPYPmT7l9AuZs5zESsFwUKHyED5e7REtPgEc8SBzqJUirEvg 24 | JmL9yR67AgMBAAECggEAAQ/IBh5fGFnL9l0sMwPI8Wxu1ra31njxLnfvAsDSfbAS 25 | K1QVIWjXSc58HRa1b7CWax9DNTvPoGl8SJVnTTlxAHKGGOTYJoyFLTf91ptlisEQ 26 | KZ3j1DYqVImsiAaGvfyz90d3imQ795Lby4EbRUcaLMcH5LatkhwS556rcelwPXuq 27 | M43XaZu5Es4pG0EmzfXplO/awt5HdUDPEAY3yw7QH8D1/l/toLPyiFv37RezkVK9 28 | ffcUQpH7uH000Gja+JSEHgpWZhE96ac6H0zBtlM1VkMtfBuczz5tkKN/p70fhr8T 29 | ZXARZqIaF4vx7RkBBzCfhvrgGqxXMuvTaW6N4RDWYQKBgQD1iZ7/xr9qy4cPFSOt 30 | yBnG5cE6wC7wP8qgr0N7MgAii5OZgx6rtfGIVJDY58CFijnT8jZ5pjNS3p7j/Rzp 31 | lQJMIwC5kIe/7FU7nmE3ko7Wg+bpd8iWLLIi/QWVFLbS7qVmulTc+CEXWyhAiI2u 32 | RL/1APjIDFKp9gqtKmwb9erxDwKBgQDf5PbGHuPv5RBLJz9du+M/BIBY+HDltG89 33 | p3huHHTjkJ5R38oximf2HnV4ygT/p2+ZUD6TJZZw6qou3/GiU5gZbRpg+4LXtQUR 34 | vV+S2n/t86NG1YcGmM29r8LWqrK9gxLW0X62Fpps16rHSP7kVc4SvmrYwqNzqKlC 35 | D9QbFYYflQKBgQCKEVzrDuNMNi43+PcbHU4BXeiOFMtQJU7XlDYp7C/PPRU+WVDB 36 | 1Yl/062vioHjlZp259hiB2cMzkoigY3kevnTvksGDZOIBGjZIXIhQbQ4Q+twlP6i 37 | E3gH3Kdq8T7s1W0EmvplVtGkxImZ4C9rMxWNu4IpW2SQVd4jCZvJDTuTWQKBgQCn 38 | LGjuCYacSubdlpKDxJSrKwtCY0641P7yhCcx4GGOwR7Vd0mbsAJsDNYduIn+8eAs 39 | E3SFnl00NqOXmHLth4lcAtDddS5/LZR5aHMCTc+TtoVFkI3faRzF84SBkLchNctN 40 | RuNbxojLmETVxDU9/Kt/51oUO1CcPWUUBImVJ38b+QKBgQCTbi0nS0n8kC7nlXWN 41 | QtPcf4UraJAxv1DGq4lnJ8AHSZqqkP5fyjfknSw5ExOPDg4mEHhnnpsvwJuSX00d 42 | UYUN2ZJXPZeaO0HmbYZ3/vC9bo6KW95PhidEUQpGlKrFY342khjQHJtH67YUThwU 43 | lQFhpxvPgPNBuxVRnsxoH/sLOA== 44 | -----END PRIVATE KEY----- 45 | -------------------------------------------------------------------------------- /apps/tests/benchmarks/confidential.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "runtime" 11 | "strconv" 12 | "sync" 13 | "text/template" 14 | "time" 15 | 16 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base" 17 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth" 18 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/fake" 19 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens" 20 | "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" 21 | ) 22 | 23 | const accessToken = "fake_token" 24 | 25 | var tokenScope = []string{"fake_scope"} 26 | 27 | type testParams struct { 28 | // the number of goroutines to use 29 | Concurrency int 30 | 31 | // the number of tokens in the cache 32 | // must be divisible by Concurrency 33 | TokenCount int 34 | } 35 | 36 | func fakeClient() (base.Client, error) { 37 | // we use a base.Client so we can provide a fake OAuth client 38 | return base.New("fake_client_id", "https://fake_authority/fake", &oauth.Client{ 39 | AccessTokens: &fake.AccessTokens{ 40 | AccessToken: accesstokens.TokenResponse{ 41 | AccessToken: accessToken, 42 | ExpiresOn: time.Now().Add(1 * time.Hour), 43 | GrantedScopes: accesstokens.Scopes{Slice: tokenScope}, 44 | }, 45 | }, 46 | Authority: &fake.Authority{ 47 | InstanceResp: authority.InstanceDiscoveryResponse{ 48 | Metadata: []authority.InstanceDiscoveryMetadata{ 49 | { 50 | PreferredNetwork: "fake_authority", 51 | Aliases: []string{"fake_authority"}, 52 | }, 53 | }, 54 | }, 55 | }, 56 | Resolver: &fake.ResolveEndpoints{ 57 | Endpoints: authority.Endpoints{ 58 | AuthorizationEndpoint: "auth_endpoint", 59 | TokenEndpoint: "token_endpoint", 60 | }, 61 | }, 62 | WSTrust: &fake.WSTrust{}, 63 | }) 64 | } 65 | 66 | type execTime struct { 67 | start time.Time 68 | end time.Time 69 | } 70 | 71 | func populateTokenCache(client base.Client, params testParams) execTime { 72 | if r := params.TokenCount % params.Concurrency; r != 0 { 73 | panic("TokenCount must be divisible by Concurrency") 74 | } 75 | parts := params.TokenCount / params.Concurrency 76 | authParams := client.AuthParams 77 | authParams.Scopes = tokenScope 78 | authParams.AuthorizationType = authority.ATClientCredentials 79 | 80 | wg := &sync.WaitGroup{} 81 | fmt.Printf("Populating token cache with %d tokens...", params.TokenCount) 82 | start := time.Now() 83 | for n := 0; n < params.Concurrency; n++ { 84 | wg.Add(1) 85 | go func(chunk int) { 86 | for i := parts * chunk; i < parts*(chunk+1); i++ { 87 | // we use this to add a fake token to the cache. 88 | // each token has a different scope which is what makes them unique 89 | _, err := client.AuthResultFromToken(context.Background(), authParams, accesstokens.TokenResponse{ 90 | AccessToken: accessToken, 91 | ExpiresOn: time.Now().Add(1 * time.Hour), 92 | GrantedScopes: accesstokens.Scopes{Slice: []string{strconv.FormatInt(int64(i), 10)}}, 93 | }) 94 | if err != nil { 95 | panic(err) 96 | } 97 | } 98 | wg.Done() 99 | }(n) 100 | } 101 | wg.Wait() 102 | return execTime{start: start, end: time.Now()} 103 | } 104 | 105 | func executeTest(client base.Client, params testParams) execTime { 106 | wg := &sync.WaitGroup{} 107 | fmt.Printf("Begin token retrieval.....") 108 | start := time.Now() 109 | for n := 0; n < params.Concurrency; n++ { 110 | wg.Add(1) 111 | go func() { 112 | // retrieve each token once per goroutine 113 | for tk := 0; tk < params.TokenCount; tk++ { 114 | _, err := client.AcquireTokenSilent(context.Background(), base.AcquireTokenSilentParameters{ 115 | Scopes: []string{strconv.FormatInt(int64(tk), 10)}, 116 | RequestType: accesstokens.ATConfidential, 117 | Credential: &accesstokens.Credential{ 118 | Secret: "fake_secret", 119 | }, 120 | }) 121 | if err != nil { 122 | panic(err) 123 | } 124 | } 125 | wg.Done() 126 | }() 127 | } 128 | wg.Wait() 129 | return execTime{start: start, end: time.Now()} 130 | } 131 | 132 | // Stats is used with statsTemplText for reporting purposes 133 | type Stats struct { 134 | popExec execTime 135 | retExec execTime 136 | Concurrency int 137 | Count int64 138 | } 139 | 140 | // PopDur returns the total duration for populating the cache. 141 | func (s *Stats) PopDur() time.Duration { 142 | return s.popExec.end.Sub(s.popExec.start) 143 | } 144 | 145 | // RetDur returns the total duration for retrieving tokens. 146 | func (s *Stats) RetDur() time.Duration { 147 | return s.retExec.end.Sub(s.retExec.start) 148 | } 149 | 150 | // PopAvg returns the mean average of caching a token. 151 | func (s *Stats) PopAvg() time.Duration { 152 | return s.PopDur() / time.Duration(s.Count) 153 | } 154 | 155 | // RetAvg returns the mean average of retrieving a token. 156 | func (s *Stats) RetAvg() time.Duration { 157 | return s.RetDur() / time.Duration(s.Count) 158 | } 159 | 160 | var statsTemplText = ` 161 | Test Results: 162 | [{{.Concurrency}} goroutines][{{.Count}} tokens] [population: total {{.PopDur}}, avg {{.PopAvg}}] [retrieval: total {{.RetDur}}, avg {{.RetAvg}}] 163 | ========================================================================== 164 | ` 165 | var statsTempl = template.Must(template.New("stats").Parse(statsTemplText)) 166 | 167 | func main() { 168 | tests := []testParams{ 169 | { 170 | Concurrency: runtime.NumCPU(), 171 | TokenCount: 100, 172 | }, 173 | { 174 | Concurrency: runtime.NumCPU(), 175 | TokenCount: 1000, 176 | }, 177 | { 178 | Concurrency: runtime.NumCPU(), 179 | TokenCount: 10000, 180 | }, 181 | { 182 | Concurrency: runtime.NumCPU(), 183 | TokenCount: 20000, 184 | }, 185 | } 186 | 187 | for _, t := range tests { 188 | client, err := fakeClient() 189 | if err != nil { 190 | panic(err) 191 | } 192 | fmt.Printf("Test Params: %#v\n", t) 193 | ptime := populateTokenCache(client, t) 194 | ttime := executeTest(client, t) 195 | if err := statsTempl.Execute(os.Stdout, &Stats{ 196 | popExec: ptime, 197 | retExec: ttime, 198 | Concurrency: t.Concurrency, 199 | Count: int64(t.TokenCount), 200 | }); err != nil { 201 | panic(err) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /apps/tests/devapps/README.md: -------------------------------------------------------------------------------- 1 | # Running the Dev Apps for MSAL Go 2 | 3 | To run one of the dev app which uses MSAL Go, the `config.json` file and the `confidential_config.json` should look like the following: 4 | 5 | ```json 6 | { 7 | "authority": "https://login.microsoftonline.com/organizations", 8 | "client_id": "your_client_id", 9 | "scopes": ["user.read"], 10 | "username": "your_username", 11 | "password": "your_password", 12 | "redirect_uri": "redirect uri registered on the portal", 13 | "code_challenge": "transformed code verifier from PKCE", 14 | "state": "state parameter for authorization code flow", 15 | "client_secret": "client secret you generated for your app", 16 | "thumbprint": "the certificate thumbprint defined in your app generation", 17 | "pem_file": "the file path of your private key pem" 18 | } 19 | ``` 20 | 21 | The dev apps in this repo get tokens for the MS Graph API. To find permissible scopes for MS Graph, visit this [link](https://docs.microsoft.com/graph/permissions-reference). PKCE is explained [here](https://tools.ietf.org/html/rfc7636#section-4.1). 22 | 23 | ## On Windows 24 | 25 | To run the dev samples: 26 | `cd test/devapps` 27 | 28 | run the command: 29 | 30 | 'go run ./ 1' 31 | 32 | Alternatives: 33 | * 1 build and run "locally" 34 | * In the devapps folder 35 | * type 'go build' 36 | * type 'devapps.exe 1' to run the device code flow 37 | 38 | * 2 (Advanced) install and run from the gobin folder 39 | * See more: https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies 40 | * In the devapps folder 41 | * type 'go install' 42 | * locate your gobin folder e.g. type 'go env' to find your gobin folder location 43 | cd to your gobin folder 44 | * type 'devapps.exe 1' to run the device code flow 45 | 46 | ## On Mac 47 | 48 | To run one of the devapps, run the command `go run src/test/devapps/*.go