diff --git a/.circleci/config.yml b/.circleci/config.yml index 795bb6614..2f5280529 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,14 +13,11 @@ jobs: steps: - checkout - setup_remote_docker - - - restore_cache: + - restore_cache: keys: - go-github-ory-fosite-v1-{{ checksum "go.sum" }} - - - run: go mod download - - - save_cache: + - run: go mod download + - save_cache: key: go-github-ory-fosite-v1-{{ checksum "go.sum" }} paths: - "/go/pkg/mod" diff --git a/.github/semantic.yml b/.github/semantic.yml index 4f8499c9f..e8a501c9c 100644 --- a/.github/semantic.yml +++ b/.github/semantic.yml @@ -1,5 +1,5 @@ -titleOnly: true -commitsOnly: false +titleOnly: false +commitsOnly: true titleAndCommits: false types: diff --git a/.github/workflows/oidc-conformity.yml b/.github/workflows/oidc-conformity.yml new file mode 100644 index 000000000..3d6372d7d --- /dev/null +++ b/.github/workflows/oidc-conformity.yml @@ -0,0 +1,24 @@ +name: "OpenID Connect Conformity Tests" + +on: + push: + +jobs: + oidc-conformity: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 + repository: ory/hydra + ref: master + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.0' + - name: Update fosite + run: go get github.com/ory/fosite@${{ github.sha }} + - name: Start service + run: ./test/conformance/start.sh + - name: Run tests + run: ./test/conformance/test.sh -v -short -parallel 16 diff --git a/CHANGELOG.md b/CHANGELOG.md index 16d246a6e..aa8d2c8d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,20 @@ **Table of Contents** -- [Unreleased (2020-10-25)](#unreleased-2020-10-25) +- [Unreleased (2020-11-18)](#unreleased-2020-11-18) +- [0.36.0 (2020-11-16)](#0360-2020-11-16) - [Bug Fixes](#bug-fixes) + - [Code Refactoring](#code-refactoring) - [Documentation](#documentation) - [Features](#features) + - [BREAKING CHANGES](#breaking-changes) - [0.35.1 (2020-10-11)](#0351-2020-10-11) - [Bug Fixes](#bug-fixes-1) - [Documentation](#documentation-1) - [Features](#features-1) - [0.35.0 (2020-10-06)](#0350-2020-10-06) - [Bug Fixes](#bug-fixes-2) - - [BREAKING CHANGES](#breaking-changes) + - [BREAKING CHANGES](#breaking-changes-1) - [0.34.1 (2020-10-02)](#0341-2020-10-02) - [Bug Fixes](#bug-fixes-3) - [Documentation](#documentation-2) @@ -22,16 +25,16 @@ - [Bug Fixes](#bug-fixes-4) - [Features](#features-2) - [Unclassified](#unclassified) - - [BREAKING CHANGES](#breaking-changes-1) + - [BREAKING CHANGES](#breaking-changes-2) - [0.33.0 (2020-09-16)](#0330-2020-09-16) - [Features](#features-3) - - [BREAKING CHANGES](#breaking-changes-2) + - [BREAKING CHANGES](#breaking-changes-3) - [0.32.4 (2020-09-15)](#0324-2020-09-15) - - [Code Refactoring](#code-refactoring) + - [Code Refactoring](#code-refactoring-1) - [Documentation](#documentation-3) - [0.32.3 (2020-09-12)](#0323-2020-09-12) - [Bug Fixes](#bug-fixes-5) - - [Code Refactoring](#code-refactoring-1) + - [Code Refactoring](#code-refactoring-2) - [Documentation](#documentation-4) - [Features](#features-4) - [0.32.2 (2020-06-22)](#0322-2020-06-22) @@ -316,28 +319,106 @@ - [0.2.0 (2016-08-06)](#020-2016-08-06) - [Unclassified](#unclassified-118) - [0.1.0 (2016-08-01)](#010-2016-08-01) - - [Code Refactoring](#code-refactoring-2) + - [Code Refactoring](#code-refactoring-3) - [Documentation](#documentation-26) - [Unclassified](#unclassified-119) -# [Unreleased](https://github.com/ory/fosite/compare/v0.35.1...5f2cae3eabb83da898e1b5515176e65dda4da862) (2020-10-25) +# Unreleased (2020-11-18) + +No significant changes have been made for this release. + + +# [0.36.0](https://github.com/ory/fosite/compare/v0.35.1...v0.36.0) (2020-11-16) ### Bug Fixes +* Allow all request object algs when client value is unset ([1d14636](https://github.com/ory/fosite/commit/1d14636e61b2047e5eee6d1d740249b819fc0794)): + + > Allows all request object signing algorithms when the client has not explicitly allowed a certain algorithm. This follows the spec: + > + > > *request_object_signing_alg - OPTIONAL. JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request Objects sent to the OP. All Request Objects from this Client MUST be rejected, if not signed with this algorithm. Request Objects are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. This algorithm MUST be used both when the Request Object is passed by value (using the request parameter) and when it is passed by reference (using the request_uri parameter). Servers SHOULD support RS256. The value none MAY be used. The default, if omitted, is that any algorithm supported by the OP and the RP MAY be used. * Always return non-error response for inactive tokens ([#517](https://github.com/ory/fosite/issues/517)) ([5f2cae3](https://github.com/ory/fosite/commit/5f2cae3eabb83da898e1b5515176e65dda4da862)) +* Be more permissive in time checks ([839d000](https://github.com/ory/fosite/commit/839d00093a2ed8c590d910f113186cd96fad9185)): + + > Time equality should not cause failures in OpenID Connect validation. +* Do not accidentally leak jwks fetching errors ([6d2092d](https://github.com/ory/fosite/commit/6d2092da1e8699e43fd6dccb4c3a33b885cec7f8)), closes [/github.com/ory/fosite/pull/526#discussion_r517491738](https://github.com//github.com/ory/fosite/pull/526/issues/discussion_r517491738) +* Do not require nonce for hybrid flows ([de5c8f9](https://github.com/ory/fosite/commit/de5c8f90e8ccae0849fa6426d53563ef7520880d)): + + > This patch resolves an issue where nonce was required for hybrid flows, which does not comply with the OpenID Connect conformity test suite, specifically the `oidcc-ensure-request-without-nonce-succeeds-for-code-flow` test. +* Guess default response mode in `NewAuthorizeRequest` ([a2952d7](https://github.com/ory/fosite/commit/a2952d7ad09fbd83a354b22dbcc0cef8a15f50f7)) +* Improve claims handling for jwts ([a72ca9a](https://github.com/ory/fosite/commit/a72ca9a978e60d7c4b000c41357719f0e2b61f8e)) +* Improve error stack wrapping ([620d4c1](https://github.com/ory/fosite/commit/620d4c148307f7be7b2674fe420141b33aef6075)) +* Kid header is not required for key lookup ([27cc5c0](https://github.com/ory/fosite/commit/27cc5c0e935ecb8bca23dd8c2670c8a93f7b829d)) +* Modernized JWT stateless introspection ([#519](https://github.com/ory/fosite/issues/519)) ([a6bfb92](https://github.com/ory/fosite/commit/a6bfb921ebc746ba7a1215e32fb42a2c0530a2bf)) +* Only use allowed characters in error_description ([431f9a5](https://github.com/ory/fosite/commit/431f9a56ed03648ea4ef637fe6c2b6d74e765dad)), closes [#525](https://github.com/ory/fosite/issues/525): + + > Replace LF and quotes with `.` and `'` to match allowed and recommended character set defined in various RFCs. +* Prevent debug details from leaking during key lookup ([c0598fb](https://github.com/ory/fosite/commit/c0598fb8d8ce75b7f0ad645420caea641e64a4d2)), closes [/github.com/ory/fosite/pull/526#discussion_r517490461](https://github.com//github.com/ory/fosite/pull/526/issues/discussion_r517490461) +* Reset jti and hash ID token claims on refresh ([#523](https://github.com/ory/fosite/issues/523)) ([ce2de73](https://github.com/ory/fosite/commit/ce2de73ff979b02be32d850c1c695067a35576c7)) +* Use state from request object ([8cac1a0](https://github.com/ory/fosite/commit/8cac1a00a6f87523b88fea6962ab1194049cbacd)): + + > Resolves failing OIDC conformity test "oidcc-request-uri-unsigned". + + +### Code Refactoring + +* Use rfc compliant error formating ([edbbda3](https://github.com/ory/fosite/commit/edbbda3c4cf70a77cdcd1383c55762c73613f87e)) ### Documentation * Document Session interface methods ([#512](https://github.com/ory/fosite/issues/512)) ([11a95ba](https://github.com/ory/fosite/commit/11a95ba00f562b3864fc0d6878c9d93943cc4273)) +* Updates banner in readme.md ([#529](https://github.com/ory/fosite/issues/529)) ([9718eb6](https://github.com/ory/fosite/commit/9718eb6ce63983ade0689908b5cce3e27c8838bc)) ### Features +* Add support for response_mode=form_post ([#509](https://github.com/ory/fosite/issues/509)) ([3e3290f](https://github.com/ory/fosite/commit/3e3290f811f849881f1c6bafabc1c765d9a42ac7)): + + > This patch introduces support for `response_mode=form_post` as well as `response_mode` of `none` and `query` and `fragment`. + > + > To support this new feature your OAuth2 Client must implement the `fosite.ResponseModeClient` interface. We suggest to always return all response modes there unless you want to explicitly disable one of the response modes: + > + > ```go + > func (c *Client) GetResponseModes() []fosite.ResponseModeType { + > return []fosite.ResponseModeType{ + > fosite.ResponseModeDefault, + > fosite.ResponseModeFormPost, + > fosite.ResponseModeQuery, + > fosite.ResponseModeFragment, + > } + > } + > ``` * Improve error messages ([#513](https://github.com/ory/fosite/issues/513)) ([fcac5a6](https://github.com/ory/fosite/commit/fcac5a6457c92d1eb1a389192cd0c7fb590ab8b3)) +* Introduce WithExposeDebug to error interface ([625a521](https://github.com/ory/fosite/commit/625a5214c4a002b4d0f86e49555edf8755703968)) +* Support passing repeated audience parameter in URL query ([#518](https://github.com/ory/fosite/issues/518)) ([47f2a31](https://github.com/ory/fosite/commit/47f2a31fbed137b58e4866f78ec8b9f591134f98)), closes [#504](https://github.com/ory/fosite/issues/504): + + > Added `GetAudiences` helper function which tries to have current behavior and also support multiple/repeated audience parameters. If there are parameter is repeated, then it is not split by space. If there is only one then it is split by space. I think this is the best balance between standard/backwards behavior and allowing repeated parameter and allowing also URIs/audiences with spaces in them (which we probably all agree is probably not something anyone should be doing). + > + > Also added `ExactAudienceMatchingStrategy` which is slightly more suitable to use for audiences which are not URIs. In [OIDC spec](https://openid.net/specs/openid-connect-core-1_0.html) audience is described as: + > + > > Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case sensitive string. + > + > `client_id` is generally not an URI, but some UUID or some other random string. + + +### BREAKING CHANGES + +* This patch removes fields `error_hint`, `error_debug` from error responses. To use the legacy error format where these fields are included, set `UseLegacyErrorFormat` to true in your compose config or directly on the `Fosite` struct. If `UseLegacyErrorFormat` is set, the `error_description` no longer merges `error_hint` nor `error_debug` messages which reverts a change introduced in `v0.33.0`. Instead, `error_hint` and `error_debug` are included and the merged message can be constructed from those fields. +* As part of this change, the error interface and its fields have changed: + +- `RFC6749Error.Name` was renamed to `RFC6749Error.ErrorField`. +- `RFC6749Error.Description` was renamed to `RFC6749Error.DescriptionField`. +- `RFC6749Error.Hint` was renamed to `RFC6749Error.HintField`. +- `RFC6749Error.Code` was renamed to `RFC6749Error.CodeField`. +- `RFC6749Error.Hint` was renamed to `RFC6749Error.HintField`. +- `RFC6749Error.WithCause()` was renamed to `RFC6749Error.WithWrap() *RFC6749Error` and alternatively to `RFC6749Error.Wrap()` (without return value) to standardize naming conventions around the new Go 1.14+ error interfaces. +* As part of this change, methods `GetResponseMode`, `SetDefaultResponseMode`, `GetDefaultResponseMode ` where added to interface `AuthorizeRequester`. Also, methods `GetQuery`, `AddQuery`, and `GetFragment` were merged into one function `GetParameters` and `AddParameter` on the `AuthorizeResponder` interface. Methods on `AuthorizeRequest` and `AuthorizeResponse` changed accordingly and will need to be updated in your codebase. Additionally, the field `Debug` was renamed to `DebugField` and a new method `Debug() string` was added to `RFC6749Error`. + +Co-authored-by: hackerman <3372410+aeneasr@users.noreply.github.com> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e946aa9f..7bd8b041a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,26 +14,31 @@ https://github.com/ory/meta/blob/master/templates/repository/CONTRIBUTING.md - [Introduction](#introduction) +- [FAQ](#faq) +- [How can I contribute?](#how-can-i-contribute) +- [Communication](#communication) - [Contributing Code](#contributing-code) +- [Documentation](#documentation) - [Disclosing vulnerabilities](#disclosing-vulnerabilities) - [Code Style](#code-style) -- [Documentation](#documentation) - [Pull request procedure](#pull-request-procedure) -- [Communication](#communication) + - [Working with Forks](#working-with-forks) - [Conduct](#conduct) ## Introduction -Please note: We take ORY {{Project}}'s security and our users' trust very +There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. + +_Please note_: We take ORY {{Project}}'s security and our users' trust very seriously. If you believe you have found a security issue in ORY {{Project}}, -please responsibly disclose by contacting us at office@ory.sh. +please responsibly disclose by contacting us at security@ory.sh. + +First: +As a potential contributor, your changes and ideas are welcome at any hour of the day or night, weekdays, weekends, and holidays. Please do not ever hesitate to ask a question or send a pull request. -First: if you're unsure or afraid of anything, just ask or submit the issue or -pull request anyways. You won't be yelled at for giving it your best effort. The -worst that can happen is that you'll be politely asked to change something. We -appreciate any sort of contributions, and don't want a wall of rules to get in +If you are unsure, just ask or submit the issue or pull request anyways. You won't be yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that. That said, if you want to ensure that a pull request is likely to be merged, @@ -42,6 +47,53 @@ won't clash or be obviated by ORY {{Project}}'s normal direction. A great way to do this is via the [ORY Community](https://community.ory.sh/) or join the [ORY Chat](https://www.ory.sh/chat). +## FAQ + +- I am new to the community. Where can I find the [ORY Community Code of Conduct?](CODE_OF_CONDUCT.md) + +- I have a question. Where can I get [answers to questions regarding ORY {{Project}}?](#communication) + +- I would like to contribute but I am not sure how. Are there [easy ways to contribute?](#how-can-i-contribute) + [Or good first issues?](https://github.com/search?l=&o=desc&q=label%3A%22help+wanted%22+label%3A%22good+first+issue%22+is%3Aopen+user%3Aory+user%3Aory-corp&s=updated&type=Issues) + +- I want to talk to other ORY {{Project}} users. [How can I become a part of the community?](#communication) + +- I would like to know what I am agreeing to when I contribute to ORY {{Project}}. Does ORY have [a Contributors License Agreement?](https://cla-assistant.io/ory/) + +- I would like updates about new versions of ORY {{Project}}. [How are new releases announced?](https://ory.us10.list-manage.com/subscribe?u=ffb1a878e4ec6c0ed312a3480&id=f605a41b53) + +## How can I contribute? + +If you want to start contributing code right away, we have a [list of good first issues](https://github.com/ory/{{Project}}/labels/good%20first%20issue). + +There are many other ways you can contribute without writing any code. Here are a few things you can do to help out: + +- **Give us a star.** It may not seem like much, but it really makes a difference. This is something that everyone can do to help out ORY {{Project}}. Github stars help the project gain visibility and stand out. + +- **Join the community.** Sometimes helping people can be as easy as listening to their problems and offering a different perspective. Join our Slack, have a look at discussions in the forum and take part in our weekly hangout. More info on this in [Communication](#communication). + +- **Helping with open issues.** + We have a lot of open issues for ORY {{Project}} and some of them may lack necessary information, some are duplicates of older issues. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information, or pointing them to existing issues that match their description of the problem. + +- **Reviewing documentation changes.** + Most documentation just needs a review for proper spelling and grammar. If you think a document can be improved in any way, feel free to hit the `edit` button at the top of the page. More info on contributing to documentation here. + +- **Help with tests.** + Some pull requests may lack proper tests or test plans. These are needed for the change to be implemented safely. + +## Communication + +We use [Slack](https://www.ory.sh/chat). You are welcome to drop in and ask +questions, discuss bugs and feature requests, talk to other users of ORY, etc. + +We have a [forum](https://community.ory.sh/). This is a great place for in-depth discussions and lots of code examples, logs and similar data. + +You can also join our weekly hangout, if you want to speak to the ORY team directly or ask some questions. You can find more info on the hangouts in [Slack](https://www.ory.sh/chat). + +If you want to receive regular notifications about updates to ORY {{Project}}, consider joining the mailing list. We will _only_ send you vital information on the projects that you are interested in. + +Also [follow us on twitter](https://twitter.com/orycorp). + ## Contributing Code Unless you are fixing a known bug, we **strongly** recommend discussing it with @@ -54,15 +106,29 @@ contributors get reviewed**. After a pull request is made other contributors will offer feedback, and if the patch passes review a maintainer will accept it with a comment. When pull requests fail testing, authors are expected to update their pull requests to address the failures until the tests pass and the pull -request merges successfully. +request merges successfully. Look here for more info on the [Pull request procedure](#pull-request-procedure). At least one review from a maintainer is required for all patches (even patches from maintainers). +Before your contributions can be merged you need to sign our [Contributor License Agreement](https://cla-assistant.io/ory/). + +This agreement defines the terms under which your code is contributed to ORY. +More specifically it declares that you have the right to, and actually do, grant us the rights to use your contribution. You can see the Apache 2.0 license under +which our projects are published [here](https://github.com/ory/meta/blob/master/LICENSE). + Reviewers should leave a "LGTM" comment once they are satisfied with the patch. If the patch was submitted by a maintainer with write access, the pull request should be merged by the submitter after review. +## Documentation + +Please provide documentation when changing, removing, or adding features. +Documentation resides in the project's [docs](docs) folder. Generate API and +configuration reference documentation using `cd docs; npm run gen`. + +For further instructions please head over to [docs/README.md](docs/README.md). + ## Disclosing vulnerabilities Please disclose vulnerabilities exclusively to @@ -76,45 +142,6 @@ Please follow these guidelines when formatting source code: - NodeJS and JavaScript code should be prettified using `npm run format` where appropriate. -## Documentation - -Please provide documentation when changing, removing, or adding features. -Documentation resides in the project's `docs` folder. - -In cases where a project does not have a `docs` folder check the README for instructions. - -The commands listed below work exclusively for projects with a `docs` folder - -### Develop - -To change the documentation locally, you need NodeJS installed and the project -checked out locally. Next, `cd` into `docs` and install the dependencies: - -```shell script -$ cd docs -$ npm install -``` - -#### Start - -To start a local development server with hot reloading, run: - -```shell script -$ npm start -``` - -This command opens up a browser window. Please note that changes to the sidebar are not hot-reloaded -and require a restart of the command. - -#### Build - -The `npm build` generates static content into the `build` directory and can be -served using any static contents hosting service. - -```shell script -$ npm build -``` - ## Pull request procedure To make a pull request, you will need a GitHub account; if you are unclear on @@ -139,34 +166,32 @@ Normally, all pull requests must include tests that test your change. Occasionally, a change will be very difficult to test for. In those cases, please include a note in your commit message explaining why. -## How We Organize Our Work +### Working with Forks -All repositories in the [ORY organization](https://github.com/ory) have their issues and pull requests -monitored in the [ORY Monitoring Board](https://github.com/orgs/ory/projects/9). This allows -for a transparent backlog of unanswered issues and pull requests across the ecosystem from those -who are allowed to merge pull requests to the main branch. +``` +# First you clone the original repository +git clone git@github.com:ory/{{Project}}.git -The process is as follows: +# Next you add a git remote that is your fork: +git remote add fork git@github.com:/{{Project}}.git -1. _Cards_ represent open issues and pull requests and are automatically assigned to the **Triage** column if - the author is not one of the maintainers and no maintainer has answered yet. -2. A maintainer assigns the issue or pull request to someone or adds the label _help wanted_ - which moves the card to **Requires Action**. -3. If a maintainer leaves a comment or review, the card moves to **Pending Reply**, implying that - the original author needs to do something (e.g. implement a change, explain something in more detail, ...). -4. If a non-maintainer pushes changes to the pull request or leaves a comment, the card moves - back to **Requires Action**. -5. If a card stays inactive for 60 days or more days, we assume that public interest in the issue - or change has waned, **archiving** the card. -6. If the issue is closed or the pull request merged or closed, the card is **archived** as well. +# Next you fetch the latest changes from origin for master: +git fetch origin +git checkout master +git pull --rebase -We try our best to answer all issues and review all pull requests and hope that this transparent way -of keeping a backlog helps you better understand how heavy the workload is. +# Next you create a new feature branch off of master: +git checkout my-feature-branch -## Communication +# Now you do your work and commit your changes: +git add -A +git commit -a -m "fix: this is the subject line" -m "This is the body line. Closes #123" -We use [Slack](https://www.ory.sh/chat). You are welcome to drop in and ask -questions, discuss bugs, etc. +# And the last step is pushing this to your fork +git push -u fork my-feature-branch +``` + +Now go to the project's GitHub Pull Request page and click "New pull request" ## Conduct diff --git a/access_error.go b/access_error.go index e48cd561d..09335dd0c 100644 --- a/access_error.go +++ b/access_error.go @@ -36,10 +36,7 @@ func (f *Fosite) writeJsonError(rw http.ResponseWriter, err error) { rw.Header().Set("Cache-Control", "no-store") rw.Header().Set("Pragma", "no-cache") - rfcerr := ErrorToRFC6749Error(err) - if !f.SendDebugMessagesToClients { - rfcerr = rfcerr.Sanitize() - } + rfcerr := ErrorToRFC6749Error(err).WithLegacyFormat(f.UseLegacyErrorFormat).WithExposeDebug(f.SendDebugMessagesToClients) js, err := json.Marshal(rfcerr) if err != nil { @@ -52,7 +49,7 @@ func (f *Fosite) writeJsonError(rw http.ResponseWriter, err error) { return } - rw.WriteHeader(rfcerr.Code) + rw.WriteHeader(rfcerr.CodeField) // ignoring the error because the connection is broken when it happens _, _ = rw.Write(js) } diff --git a/access_error_test.go b/access_error_test.go index 88d511898..1e659d535 100644 --- a/access_error_test.go +++ b/access_error_test.go @@ -60,18 +60,22 @@ func TestWriteAccessError_RFC6749(t *testing.T) { code string debug bool expectDebugMessage string + includeExtraFields bool }{ - {ErrInvalidRequest.WithDebug("some-debug"), "invalid_request", true, "some-debug"}, - {ErrInvalidRequest.WithDebugf("some-debug-%d", 1234), "invalid_request", true, "some-debug-1234"}, - {ErrInvalidRequest.WithDebug("some-debug"), "invalid_request", false, "some-debug"}, - {ErrInvalidClient.WithDebug("some-debug"), "invalid_client", false, "some-debug"}, - {ErrInvalidGrant.WithDebug("some-debug"), "invalid_grant", false, "some-debug"}, - {ErrInvalidScope.WithDebug("some-debug"), "invalid_scope", false, "some-debug"}, - {ErrUnauthorizedClient.WithDebug("some-debug"), "unauthorized_client", false, "some-debug"}, - {ErrUnsupportedGrantType.WithDebug("some-debug"), "unsupported_grant_type", false, "some-debug"}, + {ErrInvalidRequest.WithDebug("some-debug"), "invalid_request", true, "some-debug", true}, + {ErrInvalidRequest.WithDebugf("some-debug-%d", 1234), "invalid_request", true, "some-debug-1234", true}, + {ErrInvalidRequest.WithDebug("some-debug"), "invalid_request", false, "some-debug", true}, + {ErrInvalidClient.WithDebug("some-debug"), "invalid_client", false, "some-debug", true}, + {ErrInvalidGrant.WithDebug("some-debug"), "invalid_grant", false, "some-debug", true}, + {ErrInvalidScope.WithDebug("some-debug"), "invalid_scope", false, "some-debug", true}, + {ErrUnauthorizedClient.WithDebug("some-debug"), "unauthorized_client", false, "some-debug", true}, + {ErrUnsupportedGrantType.WithDebug("some-debug"), "unsupported_grant_type", false, "some-debug", true}, + {ErrUnsupportedGrantType.WithDebug("some-debug"), "unsupported_grant_type", false, "some-debug", false}, + {ErrUnsupportedGrantType.WithDebug("some-debug"), "unsupported_grant_type", true, "some-debug", false}, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { f.SendDebugMessagesToClients = c.debug + f.UseLegacyErrorFormat = c.includeExtraFields rw := httptest.NewRecorder() f.WriteAccessError(rw, nil, c.err) @@ -88,19 +92,26 @@ func TestWriteAccessError_RFC6749(t *testing.T) { require.NoError(t, err) assert.Equal(t, c.code, params.Error) - assert.Equal(t, c.err.Hint, params.Hint) - - expectDescription := c.err.Description - if c.err.Hint != "" { - expectDescription += "\n\n" + c.err.Hint - } - - if !c.debug { - assert.Equal(t, expectDescription, params.Description) + if !c.includeExtraFields { assert.Empty(t, params.Debug) + assert.Empty(t, params.Hint) + assert.Contains(t, params.Description, c.err.DescriptionField) + assert.Contains(t, params.Description, c.err.HintField) + + if c.debug { + assert.Contains(t, params.Description, c.err.DebugField) + } else { + assert.NotContains(t, params.Description, c.err.DebugField) + } } else { - assert.Equal(t, expectDescription+"\n\n"+c.expectDebugMessage, params.Description) - assert.Equal(t, c.expectDebugMessage, params.Debug) + assert.EqualValues(t, c.err.DescriptionField, params.Description) + assert.EqualValues(t, c.err.HintField, params.Hint) + + if !c.debug { + assert.Empty(t, params.Debug) + } else { + assert.EqualValues(t, c.err.DebugField, params.Debug) + } } }) } diff --git a/access_request_handler.go b/access_request_handler.go index a843c229f..8b92b5c35 100644 --- a/access_request_handler.go +++ b/access_request_handler.go @@ -26,6 +26,8 @@ import ( "net/http" "strings" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" ) @@ -59,11 +61,11 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session accessRequest := NewAccessRequest(session) if r.Method != "POST" { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return accessRequest, errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s', expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) + return accessRequest, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) + return accessRequest, errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) } accessRequest.Form = r.PostForm @@ -72,10 +74,10 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session } accessRequest.SetRequestedScopes(RemoveEmpty(strings.Split(r.PostForm.Get("scope"), " "))) - accessRequest.SetRequestedAudience(RemoveEmpty(strings.Split(r.PostForm.Get("audience"), " "))) + accessRequest.SetRequestedAudience(GetAudiences(r.PostForm)) accessRequest.GrantTypes = RemoveEmpty(strings.Split(r.PostForm.Get("grant_type"), " ")) if len(accessRequest.GrantTypes) < 1 { - return accessRequest, errors.WithStack(ErrInvalidRequest.WithHint(`Request parameter "grant_type"" is missing`)) + return accessRequest, errorsx.WithStack(ErrInvalidRequest.WithHint("Request parameter 'grant_type' is missing")) } client, err := f.AuthenticateClient(ctx, r, r.PostForm) @@ -96,7 +98,7 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session } if !found { - return nil, errors.WithStack(ErrInvalidRequest) + return nil, errorsx.WithStack(ErrInvalidRequest) } return accessRequest, nil } diff --git a/access_response_writer.go b/access_response_writer.go index 2374cc2e5..0547266d2 100644 --- a/access_response_writer.go +++ b/access_response_writer.go @@ -24,6 +24,8 @@ package fosite import ( "context" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" ) @@ -43,7 +45,7 @@ func (f *Fosite) NewAccessResponse(ctx context.Context, requester AccessRequeste } if response.GetAccessToken() == "" || response.GetTokenType() == "" { - return nil, errors.WithStack(ErrServerError.WithHint("An internal server occurred while trying to complete the request.").WithDebug("Access token or token type not set by TokenEndpointHandlers.")) + return nil, errorsx.WithStack(ErrServerError.WithHint("An internal server occurred while trying to complete the request.").WithDebug("Access token or token type not set by TokenEndpointHandlers.")) } return response, nil diff --git a/audience_strategy.go b/audience_strategy.go index 0386970cc..977219fcd 100644 --- a/audience_strategy.go +++ b/audience_strategy.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" ) type AudienceMatchingStrategy func(haystack []string, needle []string) error @@ -18,14 +18,14 @@ func DefaultAudienceMatchingStrategy(haystack []string, needle []string) error { for _, n := range needle { nu, err := url.Parse(n) if err != nil { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Unable to parse requested audience "%s".`, n).WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to parse requested audience '%s'.", n).WithWrap(err).WithDebug(err.Error())) } var found bool for _, h := range haystack { hu, err := url.Parse(h) if err != nil { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Unable to parse whitelisted audience "%s".`, h).WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to parse whitelisted audience '%s'.", h).WithWrap(err).WithDebug(err.Error())) } allowedPath := strings.TrimRight(hu.Path, "/") @@ -39,15 +39,57 @@ func DefaultAudienceMatchingStrategy(haystack []string, needle []string) error { } if !found { - return errors.WithStack(ErrInvalidRequest.WithHintf(`Requested audience "%s" has not been whitelisted by the OAuth 2.0 Client.`, n)) + return errorsx.WithStack(ErrInvalidRequest.WithHintf("Requested audience '%s' has not been whitelisted by the OAuth 2.0 Client.", n)) } } return nil } +// ExactAudienceMatchingStrategy does not assume that audiences are URIs, but compares strings as-is and +// does matching with exact string comparison. It requires that all strings in "needle" are present in +// "haystack". Use this strategy when your audience values are not URIs (e.g., you use client IDs for +// audience and they are UUIDs or random strings). +func ExactAudienceMatchingStrategy(haystack []string, needle []string) error { + if len(needle) == 0 { + return nil + } + + for _, n := range needle { + var found bool + for _, h := range haystack { + if n == h { + found = true + } + } + + if !found { + return errorsx.WithStack(ErrInvalidRequest.WithHintf(`Requested audience "%s" has not been whitelisted by the OAuth 2.0 Client.`, n)) + } + } + + return nil +} + +// GetAudiences allows audiences to be provided as repeated "audience" form parameter, +// or as a space-delimited "audience" form parameter if it is not repeated. +// RFC 8693 in section 2.1 specifies that multiple audience values should be multiple +// query parameters, while RFC 6749 says that that request parameter must not be included +// more than once (and thus why we use space-delimited value). This function tries to satisfy both. +// If "audience" form parameter is repeated, we do not split the value by space. +func GetAudiences(form url.Values) []string { + audiences := form["audience"] + if len(audiences) > 1 { + return RemoveEmpty(audiences) + } else if len(audiences) == 1 { + return RemoveEmpty(strings.Split(audiences[0], " ")) + } else { + return []string{} + } +} + func (f *Fosite) validateAuthorizeAudience(r *http.Request, request *AuthorizeRequest) error { - audience := RemoveEmpty(strings.Split(request.Form.Get("audience"), " ")) + audience := GetAudiences(request.Form) if err := f.AudienceMatchingStrategy(request.Client.GetAudience(), audience); err != nil { return err diff --git a/audience_strategy_test.go b/audience_strategy_test.go index 87d7d0afd..8f8412857 100644 --- a/audience_strategy_test.go +++ b/audience_strategy_test.go @@ -83,6 +83,41 @@ func TestDefaultAudienceMatchingStrategy(t *testing.T) { n: []string{"https://cloud.ory.xyz/api/users"}, err: true, }, + { + h: []string{"foobar"}, + n: []string{"foobar"}, + err: false, + }, + { + h: []string{"foo bar"}, + n: []string{"foo bar"}, + err: false, + }, + { + h: []string{"foobar"}, + n: []string{"foobar"}, + err: false, + }, + { + h: []string{"zoo", "bar"}, + n: []string{"zoo"}, + err: false, + }, + { + h: []string{"zoo"}, + n: []string{"zoo", "bar"}, + err: true, + }, + { + h: []string{"foobar"}, + n: []string{"foobar/"}, + err: false, + }, + { + h: []string{"foobar/"}, + n: []string{"foobar"}, + err: false, + }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { err := DefaultAudienceMatchingStrategy(tc.h, tc.n) @@ -94,3 +129,126 @@ func TestDefaultAudienceMatchingStrategy(t *testing.T) { }) } } + +func TestExactAudienceMatchingStrategy(t *testing.T) { + for k, tc := range []struct { + h []string + n []string + err bool + }{ + { + h: []string{}, + n: []string{}, + err: false, + }, + { + h: []string{"http://foo/bar"}, + n: []string{}, + err: false, + }, + { + h: []string{}, + n: []string{"http://foo/bar"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh/api/users"}, + err: false, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh/api/users/"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users/"}, + n: []string{"https://cloud.ory.sh/api/users/"}, + err: false, + }, + { + h: []string{"https://cloud.ory.sh/api/users/"}, + n: []string{"https://cloud.ory.sh/api/users"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh/api/users/1234"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh/api/users", "https://cloud.ory.sh/api/users/", "https://cloud.ory.sh/api/users/1234"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users", "https://cloud.ory.sh/api/tenants"}, + n: []string{"https://cloud.ory.sh/api/users", "https://cloud.ory.sh/api/users/", "https://cloud.ory.sh/api/users/1234", "https://cloud.ory.sh/api/tenants"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh/api/users1234"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"http://cloud.ory.sh/api/users"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.sh:8000/api/users"}, + err: true, + }, + { + h: []string{"https://cloud.ory.sh/api/users"}, + n: []string{"https://cloud.ory.xyz/api/users"}, + err: true, + }, + { + h: []string{"foobar"}, + n: []string{"foobar"}, + err: false, + }, + { + h: []string{"foo bar"}, + n: []string{"foo bar"}, + err: false, + }, + { + h: []string{"foobar"}, + n: []string{"foobar"}, + err: false, + }, + { + h: []string{"zoo", "bar"}, + n: []string{"zoo"}, + err: false, + }, + { + h: []string{"zoo"}, + n: []string{"zoo", "bar"}, + err: true, + }, + { + h: []string{"foobar"}, + n: []string{"foobar/"}, + err: true, + }, + { + h: []string{"foobar/"}, + n: []string{"foobar"}, + err: true, + }, + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + err := ExactAudienceMatchingStrategy(tc.h, tc.n) + if tc.err { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/authorize_error.go b/authorize_error.go index 3608ca480..ff463ad5a 100644 --- a/authorize_error.go +++ b/authorize_error.go @@ -25,19 +25,13 @@ import ( "encoding/json" "fmt" "net/http" - - "github.com/pkg/errors" ) func (f *Fosite) WriteAuthorizeError(rw http.ResponseWriter, ar AuthorizeRequester, err error) { rw.Header().Set("Cache-Control", "no-store") rw.Header().Set("Pragma", "no-cache") - rfcerr := ErrorToRFC6749Error(err) - if !f.SendDebugMessagesToClients { - rfcerr = rfcerr.Sanitize() - } - + rfcerr := ErrorToRFC6749Error(err).WithLegacyFormat(f.UseLegacyErrorFormat).WithExposeDebug(f.SendDebugMessagesToClients) if !ar.IsRedirectURIValid() { rw.Header().Set("Content-Type", "application/json;charset=UTF-8") @@ -52,7 +46,7 @@ func (f *Fosite) WriteAuthorizeError(rw http.ResponseWriter, ar AuthorizeRequest return } - rw.WriteHeader(rfcerr.Code) + rw.WriteHeader(rfcerr.CodeField) _, _ = rw.Write(js) return } @@ -62,22 +56,26 @@ func (f *Fosite) WriteAuthorizeError(rw http.ResponseWriter, ar AuthorizeRequest // The endpoint URI MUST NOT include a fragment component. redirectURI.Fragment = "" - query := rfcerr.ToValues() - query.Add("state", ar.GetState()) + errors := rfcerr.ToValues() + errors.Set("state", ar.GetState()) var redirectURIString string - if !(len(ar.GetResponseTypes()) == 0 || ar.GetResponseTypes().ExactOne("code")) && !errors.Is(err, ErrUnsupportedResponseType) { - redirectURIString = redirectURI.String() + "#" + query.Encode() + if ar.GetResponseMode() == ResponseModeFormPost { + rw.Header().Set("Content-Type", "text/html;charset=UTF-8") + WriteAuthorizeFormPostResponse(redirectURI.String(), errors, GetPostFormHTMLTemplate(*f), rw) + return + } else if ar.GetResponseMode() == ResponseModeFragment { + redirectURIString = redirectURI.String() + "#" + errors.Encode() } else { for key, values := range redirectURI.Query() { for _, value := range values { - query.Add(key, value) + errors.Add(key, value) } } - redirectURI.RawQuery = query.Encode() + redirectURI.RawQuery = errors.Encode() redirectURIString = redirectURI.String() } - rw.Header().Add("Location", redirectURIString) + rw.Header().Set("Location", redirectURIString) rw.WriteHeader(http.StatusFound) } diff --git a/authorize_error_test.go b/authorize_error_test.go index 207772b4e..ce072ba71 100644 --- a/authorize_error_test.go +++ b/authorize_error_test.go @@ -61,11 +61,13 @@ func TestWriteAuthorizeError(t *testing.T) { header := http.Header{} for k, c := range []struct { - err error - debug bool - mock func(*MockResponseWriter, *MockAuthorizeRequester) - checkHeader func(*testing.T, int) + err *RFC6749Error + debug bool + doNotUseLegacyFormat bool + mock func(*MockResponseWriter, *MockAuthorizeRequester) + checkHeader func(*testing.T, int) }{ + // 0 { err: ErrInvalidGrant, mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { @@ -80,6 +82,7 @@ func TestWriteAuthorizeError(t *testing.T) { assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 1 { debug: true, err: ErrInvalidRequest.WithDebug("with-debug"), @@ -87,144 +90,272 @@ func TestWriteAuthorizeError(t *testing.T) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseMode().Return(ResponseModeQuery).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 2 + { + debug: true, + doNotUseLegacyFormat: true, + err: ErrInvalidRequest.WithDebug("with-debug"), + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseMode().Return(ResponseModeQuery).AnyTimes() + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + checkHeader: func(t *testing.T, k int) { + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&state=foostate") + b, _ := url.Parse(header.Get("Location")) + assert.Equal(t, a, b) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 3 + { + doNotUseLegacyFormat: true, + err: ErrInvalidRequest.WithDebug("with-debug"), + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseMode().Return(ResponseModeQuery).AnyTimes() + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + checkHeader: func(t *testing.T, k int) { + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + b, _ := url.Parse(header.Get("Location")) + assert.Equal(t, a, b) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 4 { err: ErrInvalidRequest.WithDebug("with-debug"), mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseMode().Return(ResponseModeDefault).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 5 { err: ErrInvalidRequest, mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code"})) + req.EXPECT().GetResponseMode().Return(ResponseModeQuery).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&foo=bar&state=foostate") + a, _ := url.Parse("https://foobar.com/?error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&foo=bar&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 6 { err: ErrUnsupportedGrantType, mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"foobar"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"foobar"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=unsupported_grant_type&error_description=The+authorization+grant+type+is+not+supported+by+the+authorization+server&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=unsupported_grant_type&error_description=The+authorization+grant+type+is+not+supported+by+the+authorization+server.&state=foostate") b, _ := url.Parse(header.Get("Location")) assert.Equal(t, a, b) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 7 { err: ErrInvalidRequest, mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 8 { err: ErrInvalidRequest, mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 9 { - err: ErrInvalidRequest, + err: ErrInvalidRequest.WithDebug("with-debug"), mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 10 + { + err: ErrInvalidRequest.WithDebug("with-debug"), + debug: true, + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + checkHeader: func(t *testing.T, k int) { + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + b, _ := url.Parse(header.Get("Location")) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 11 + { + err: ErrInvalidRequest.WithDebug("with-debug"), + debug: true, + doNotUseLegacyFormat: true, + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + checkHeader: func(t *testing.T, k int) { + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.+with-debug&state=foostate") + b, _ := url.Parse(header.Get("Location")) + assert.NotContains(t, header.Get("Location"), "error_hint") + assert.NotContains(t, header.Get("Location"), "error_debug") + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 12 + { + err: ErrInvalidRequest.WithDebug("with-debug"), + doNotUseLegacyFormat: true, + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[0])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + checkHeader: func(t *testing.T, k int) { + a, _ := url.Parse("https://foobar.com/#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.+Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + b, _ := url.Parse(header.Get("Location")) + assert.NotContains(t, header.Get("Location"), "error_hint") + assert.NotContains(t, header.Get("Location"), "error_debug") + assert.NotContains(t, header.Get("Location"), "with-debug") + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 13 { err: ErrInvalidRequest.WithDebug("with-debug"), mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 14 { debug: true, err: ErrInvalidRequest.WithDebug("with-debug"), @@ -232,18 +363,20 @@ func TestWriteAuthorizeError(t *testing.T) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"code", "token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 15 { debug: true, err: ErrInvalidRequest.WithDebug("with-debug"), @@ -251,18 +384,20 @@ func TestWriteAuthorizeError(t *testing.T) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"id_token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"id_token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) }, }, + // 16 { debug: true, err: ErrInvalidRequest.WithDebug("with-debug"), @@ -270,22 +405,43 @@ func TestWriteAuthorizeError(t *testing.T) { req.EXPECT().IsRedirectURIValid().Return(true) req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) req.EXPECT().GetState().Return("foostate") - req.EXPECT().GetResponseTypes().MaxTimes(2).Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFragment).AnyTimes() rw.EXPECT().Header().Times(3).Return(header) rw.EXPECT().WriteHeader(http.StatusFound) }, checkHeader: func(t *testing.T, k int) { - a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed%0A%0AMake+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.%0A%0Awith-debug&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") + a, _ := url.Parse("https://foobar.com/?foo=bar#error=invalid_request&error_debug=with-debug&error_description=The+request+is+missing+a+required+parameter%2C+includes+an+invalid+parameter+value%2C+includes+a+parameter+more+than+once%2C+or+is+otherwise+malformed.&error_hint=Make+sure+that+the+various+parameters+are+correct%2C+be+aware+of+case+sensitivity+and+trim+your+parameters.+Make+sure+that+the+client+you+are+using+has+exactly+whitelisted+the+redirect_uri+you+specified.&state=foostate") b, _ := url.Parse(header.Get("Location")) - assert.Equal(t, a, b) + assert.Equal(t, a, b, "\n\t%s\n\t%s", header.Get("Location"), a.String()) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "no-cache", header.Get("Pragma")) + }, + }, + // 17 + { + debug: true, + err: ErrInvalidRequest.WithDebug("with-debug"), + mock: func(rw *MockResponseWriter, req *MockAuthorizeRequester) { + req.EXPECT().IsRedirectURIValid().Return(true) + req.EXPECT().GetRedirectURI().Return(copyUrl(purls[1])) + req.EXPECT().GetState().Return("foostate") + req.EXPECT().GetResponseTypes().AnyTimes().Return(Arguments([]string{"token"})) + req.EXPECT().GetResponseMode().Return(ResponseModeFormPost).Times(1) + rw.EXPECT().Header().Times(3).Return(header) + rw.EXPECT().Write(gomock.Any()).AnyTimes() + }, + checkHeader: func(t *testing.T, k int) { assert.Equal(t, "no-store", header.Get("Cache-Control")) assert.Equal(t, "no-cache", header.Get("Pragma")) + assert.Equal(t, "text/html;charset=UTF-8", header.Get("Content-Type")) }, }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { oauth2 := &Fosite{ SendDebugMessagesToClients: c.debug, + UseLegacyErrorFormat: !c.doNotUseLegacyFormat, } ctrl := gomock.NewController(t) diff --git a/authorize_helper.go b/authorize_helper.go index 1647ccbb9..9eadbb6b4 100644 --- a/authorize_helper.go +++ b/authorize_helper.go @@ -22,14 +22,33 @@ package fosite import ( + "fmt" + "html/template" + "io" "net/url" "regexp" "strings" + "github.com/ory/x/errorsx" + "github.com/asaskevich/govalidator" - "github.com/pkg/errors" ) +var FormPostDefaultTemplate = template.Must(template.New("form_post").Parse(` + + Submit This Form + + +
+ {{ range $key,$value := .Parameters }} + {{ range $parameter:= $value}} + + {{end}} + {{ end }} +
+ +`)) + // MatchRedirectURIWithClientRedirectURIs if the given uri is a registered redirect uri. Does not perform // uri validation. // @@ -75,7 +94,7 @@ func MatchRedirectURIWithClientRedirectURIs(rawurl string, client Client) (*url. } } - return nil, errors.WithStack(ErrInvalidRequest.WithHint(`The "redirect_uri" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.`)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHint("The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.")) } // Match a requested redirect URI against a pool of registered client URIs @@ -182,3 +201,35 @@ func IsLocalhost(redirectURI *url.URL) bool { hn := redirectURI.Hostname() return strings.HasSuffix(hn, ".localhost") || hn == "127.0.0.1" || hn == "::1" || hn == "localhost" } + +func WriteAuthorizeFormPostResponse(redirectURL string, parameters url.Values, template *template.Template, rw io.Writer) { + _ = template.Execute(rw, struct { + RedirURL string + Parameters url.Values + }{ + RedirURL: redirectURL, + Parameters: parameters, + }) +} + +func URLSetFragment(source *url.URL, fragment url.Values) { + var f string + for k, v := range fragment { + for _, vv := range v { + if len(f) != 0 { + f += fmt.Sprintf("&%s=%s", k, vv) + } else { + f += fmt.Sprintf("%s=%s", k, vv) + } + } + } + source.Fragment = f +} + +func GetPostFormHTMLTemplate(f Fosite) *template.Template { + formPostHTMLTemplate := f.FormPostHTMLTemplate + if formPostHTMLTemplate == nil { + formPostHTMLTemplate = FormPostDefaultTemplate + } + return formPostHTMLTemplate +} diff --git a/authorize_helper_test.go b/authorize_helper_test.go index 9765e3889..4612e0d68 100644 --- a/authorize_helper_test.go +++ b/authorize_helper_test.go @@ -19,12 +19,18 @@ * */ -package fosite +package fosite_test import ( + "bytes" + "io/ioutil" "net/url" + "strings" "testing" + "github.com/ory/fosite" + "github.com/ory/fosite/internal" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -43,7 +49,7 @@ func TestIsLocalhost(t *testing.T) { {expect: true, rawurl: "https://test.localhost"}, } { u, _ := url.Parse(c.rawurl) - assert.Equal(t, c.expect, IsLocalhost(u), "case %d", k) + assert.Equal(t, c.expect, fosite.IsLocalhost(u), "case %d", k) } } @@ -60,172 +66,172 @@ func TestIsLocalhost(t *testing.T) { // of pre-registered redirect URIs (see Section 5.2.3.5). func TestDoesClientWhiteListRedirect(t *testing.T) { for k, c := range []struct { - client Client + client fosite.Client url string isError bool expected string }{ { - client: &DefaultClient{RedirectURIs: []string{""}}, + client: &fosite.DefaultClient{RedirectURIs: []string{""}}, url: "https://foo.com/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"wta://auth"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"wta://auth"}}, url: "wta://auth", expected: "wta://auth", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"wta:///auth"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"wta:///auth"}}, url: "wta:///auth", expected: "wta:///auth", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"wta://foo/auth"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"wta://foo/auth"}}, url: "wta://foo/auth", expected: "wta://foo/auth", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, url: "https://foo.com/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, url: "", isError: false, expected: "https://bar.com/cb", }, { - client: &DefaultClient{RedirectURIs: []string{""}}, + client: &fosite.DefaultClient{RedirectURIs: []string{""}}, url: "", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, url: "https://bar.com/cb", isError: false, expected: "https://bar.com/cb", }, { - client: &DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}}, url: "https://bar.com/cb123", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://[::1]"}}, url: "http://[::1]:1024", expected: "http://[::1]:1024", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://[::1]"}}, url: "http://[::1]:1024/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://[::1]/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://[::1]/cb"}}, url: "http://[::1]:1024/cb", expected: "http://[::1]:1024/cb", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://[::1]"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://[::1]"}}, url: "http://foo.bar/bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, url: "http://127.0.0.1:1024", expected: "http://127.0.0.1:1024", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1/cb"}}, url: "http://127.0.0.1:64000/cb", expected: "http://127.0.0.1:64000/cb", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, url: "http://127.0.0.1:64000/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, url: "http://127.0.0.1", expected: "http://127.0.0.1", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1/Cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1/Cb"}}, url: "http://127.0.0.1:8080/Cb", expected: "http://127.0.0.1:8080/Cb", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, url: "http://foo.bar/bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1"}}, url: ":/invalid.uri)bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, url: "http://127.0.0.1:8080/Cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, url: "http://127.0.0.1:8080/cb?foo=bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar"}}, url: "http://127.0.0.1:8080/cb?foo=bar", expected: "http://127.0.0.1:8080/cb?foo=bar", isError: false, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar"}}, url: "http://127.0.0.1:8080/cb?baz=bar&foo=bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar&baz=bar"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb?foo=bar&baz=bar"}}, url: "http://127.0.0.1:8080/cb?baz=bar&foo=bar", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"https://www.ory.sh/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://www.ory.sh/cb"}}, url: "http://127.0.0.1:8080/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"http://127.0.0.1:8080/cb"}}, url: "https://www.ory.sh/cb", isError: true, }, { - client: &DefaultClient{RedirectURIs: []string{"web+application://callback"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"web+application://callback"}}, url: "web+application://callback", isError: false, expected: "web+application://callback", }, { - client: &DefaultClient{RedirectURIs: []string{"https://google.com/?foo=bar%20foo+baz"}}, + client: &fosite.DefaultClient{RedirectURIs: []string{"https://google.com/?foo=bar%20foo+baz"}}, url: "https://google.com/?foo=bar%20foo+baz", isError: false, expected: "https://google.com/?foo=bar%20foo+baz", }, } { - redir, err := MatchRedirectURIWithClientRedirectURIs(c.url, c.client) + redir, err := fosite.MatchRedirectURIWithClientRedirectURIs(c.url, c.client) assert.Equal(t, c.isError, err != nil, "%d: %+v", k, c) if err == nil { require.NotNil(t, redir, "%d", k) @@ -253,7 +259,52 @@ func TestIsRedirectURISecure(t *testing.T) { } { uu, err := url.Parse(c.u) require.NoError(t, err) - assert.Equal(t, !c.err, IsRedirectURISecure(uu), "case %d", d) + assert.Equal(t, !c.err, fosite.IsRedirectURISecure(uu), "case %d", d) + } +} + +func TestWriteAuthorizeFormPostResponse(t *testing.T) { + for d, c := range []struct { + parameters url.Values + check func(code string, state string, customParams url.Values, d int) + }{ + { + parameters: url.Values{"code": {"lshr755nsg39fgur"}, "state": {"924659540232"}}, + check: func(code string, state string, customParams url.Values, d int) { + assert.Equal(t, "lshr755nsg39fgur", code, "case %d", d) + assert.Equal(t, "924659540232", state, "case %d", d) + }, + }, + { + parameters: url.Values{"code": {"lshr75*ns-39f+ur"}, "state": {"9a:* <&)"}}, + check: func(code string, state string, customParams url.Values, d int) { + assert.Equal(t, "lshr75*ns-39f+ur", code, "case %d", d) + assert.Equal(t, "9a:* <&)", state, "case %d", d) + }, + }, + { + parameters: url.Values{"code": {"1234"}, "custom": {"test2", "test3"}}, + check: func(code string, state string, customParams url.Values, d int) { + assert.Equal(t, "1234", code, "case %d", d) + assert.Equal(t, []string{"test2", "test3"}, customParams["custom"], "case %d", d) + }, + }, + { + parameters: url.Values{"code": {"1234"}, "custom": {"Bold"}}, + check: func(code string, state string, customParams url.Values, d int) { + assert.Equal(t, "1234", code, "case %d", d) + assert.Equal(t, "Bold", customParams.Get("custom"), "case %d", d) + }, + }, + } { + var responseBuffer bytes.Buffer + redirectURL := "https://localhost:8080/cb" + //parameters := + fosite.WriteAuthorizeFormPostResponse(redirectURL, c.parameters, fosite.FormPostDefaultTemplate, &responseBuffer) + code, state, _, _, customParams, _, err := internal.ParseFormPostResponse(redirectURL, ioutil.NopCloser(bytes.NewReader(responseBuffer.Bytes()))) + assert.NoError(t, err, "case %d", d) + c.check(code, state, customParams, d) + } } @@ -275,6 +326,40 @@ func TestIsRedirectURISecureStrict(t *testing.T) { } { uu, err := url.Parse(c.u) require.NoError(t, err) - assert.Equal(t, !c.err, IsRedirectURISecureStrict(uu), "case %d", d) + assert.Equal(t, !c.err, fosite.IsRedirectURISecureStrict(uu), "case %d", d) + } +} + +func TestURLSetFragment(t *testing.T) { + for d, c := range []struct { + u string + a string + f url.Values + }{ + {u: "http://google.com", a: "http://google.com#code=567060896", f: url.Values{"code": []string{"567060896"}}}, + {u: "http://google.com", a: "http://google.com#code=567060896&scope=read", f: url.Values{"code": []string{"567060896"}, "scope": []string{"read"}}}, + {u: "http://google.com", a: "http://google.com#code=567060896&scope=read%20mail", f: url.Values{"code": []string{"567060896j"}, "scope": []string{"read mail"}}}, + {u: "http://google.com", a: "http://google.com#code=567060896&scope=read+write", f: url.Values{"code": []string{"567060896"}, "scope": []string{"read+write"}}}, + {u: "http://google.com", a: "http://google.com#code=567060896&scope=api:*", f: url.Values{"code": []string{"567060896"}, "scope": []string{"api:*"}}}, + {u: "https://google.com?foo=bar", a: "https://google.com?foo=bar#code=567060896", f: url.Values{"code": []string{"567060896"}}}, + {u: "http://localhost?foo=bar&baz=foo", a: "http://localhost?foo=bar&baz=foo#code=567060896", f: url.Values{"code": []string{"567060896"}}}, + } { + uu, err := url.Parse(c.u) + require.NoError(t, err) + fosite.URLSetFragment(uu, c.f) + tURL, err := url.Parse(uu.String()) + require.NoError(t, err) + r := ParseURLFragment(tURL.Fragment) + assert.Equal(t, c.f.Get("code"), r.Get("code"), "case %d", d) + assert.Equal(t, c.f.Get("scope"), r.Get("scope"), "case %d", d) + } +} +func ParseURLFragment(fragment string) url.Values { + r := url.Values{} + kvs := strings.Split(fragment, "&") + for _, kv := range kvs { + kva := strings.Split(kv, "=") + r.Add(kva[0], kva[1]) } + return r } diff --git a/authorize_request.go b/authorize_request.go index dade3a3a4..3c047f211 100644 --- a/authorize_request.go +++ b/authorize_request.go @@ -25,12 +25,23 @@ import ( "net/url" ) +type ResponseModeType string + +const ( + ResponseModeDefault = ResponseModeType("") + ResponseModeFormPost = ResponseModeType("form_post") + ResponseModeQuery = ResponseModeType("query") + ResponseModeFragment = ResponseModeType("fragment") +) + // AuthorizeRequest is an implementation of AuthorizeRequester type AuthorizeRequest struct { - ResponseTypes Arguments `json:"responseTypes" gorethink:"responseTypes"` - RedirectURI *url.URL `json:"redirectUri" gorethink:"redirectUri"` - State string `json:"state" gorethink:"state"` - HandledResponseTypes Arguments `json:"handledResponseTypes" gorethink:"handledResponseTypes"` + ResponseTypes Arguments `json:"responseTypes" gorethink:"responseTypes"` + RedirectURI *url.URL `json:"redirectUri" gorethink:"redirectUri"` + State string `json:"state" gorethink:"state"` + HandledResponseTypes Arguments `json:"handledResponseTypes" gorethink:"handledResponseTypes"` + ResponseMode ResponseModeType `json:"ResponseModes" gorethink:"ResponseModes"` + DefaultResponseMode ResponseModeType `json:"DefaultResponseMode" gorethink:"DefaultResponseMode"` Request } @@ -41,6 +52,7 @@ func NewAuthorizeRequest() *AuthorizeRequest { RedirectURI: &url.URL{}, HandledResponseTypes: Arguments{}, Request: *NewRequest(), + ResponseMode: ResponseModeDefault, } } @@ -86,3 +98,18 @@ func (d *AuthorizeRequest) DidHandleAllResponseTypes() bool { return len(d.ResponseTypes) > 0 } + +func (d *AuthorizeRequest) GetResponseMode() ResponseModeType { + return d.ResponseMode +} + +func (d *AuthorizeRequest) SetDefaultResponseMode(defaultResponseMode ResponseModeType) { + if d.ResponseMode == ResponseModeDefault { + d.ResponseMode = defaultResponseMode + } + d.DefaultResponseMode = defaultResponseMode +} + +func (d *AuthorizeRequest) GetDefaultResponseMode() ResponseModeType { + return d.DefaultResponseMode +} diff --git a/authorize_request_handler.go b/authorize_request_handler.go index 18a15a82d..271a644d6 100644 --- a/authorize_request_handler.go +++ b/authorize_request_handler.go @@ -28,12 +28,22 @@ import ( "net/http" "strings" + "github.com/ory/x/errorsx" + jwt "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" "github.com/ory/go-convenience/stringslice" ) +func wrapSigningKeyFailure(outer *RFC6749Error, inner error) *RFC6749Error { + outer = outer.WithWrap(inner).WithDebug(inner.Error()) + if e := new(RFC6749Error); errors.As(inner, &e) { + return outer.WithHintf("%s %s", outer.Reason(), e.Reason()) + } + return outer +} + func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *AuthorizeRequest) error { var scope Arguments = RemoveEmpty(strings.Split(request.Form.Get("scope"), " ")) @@ -47,25 +57,25 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut if len(request.Form.Get("request")+request.Form.Get("request_uri")) == 0 { return nil } else if len(request.Form.Get("request")) > 0 && len(request.Form.Get("request_uri")) > 0 { - return errors.WithStack(ErrInvalidRequest.WithHint(`OpenID Connect parameters "request" and "request_uri" were both given, but you can use at most one.`)) + return errorsx.WithStack(ErrInvalidRequest.WithHint("OpenID Connect parameters 'request' and 'request_uri' were both given, but you can use at most one.")) } oidcClient, ok := request.Client.(OpenIDConnectClient) if !ok { if len(request.Form.Get("request_uri")) > 0 { - return errors.WithStack(ErrRequestURINotSupported.WithHint(`OpenID Connect "request_uri" context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.`)) + return errorsx.WithStack(ErrRequestURINotSupported.WithHint("OpenID Connect 'request_uri' context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.")) } - return errors.WithStack(ErrRequestNotSupported.WithHint(`OpenID Connect "request" context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.`)) + return errorsx.WithStack(ErrRequestNotSupported.WithHint("OpenID Connect 'request' context was given, but the OAuth 2.0 Client does not implement advanced OpenID Connect capabilities.")) } if oidcClient.GetJSONWebKeys() == nil && len(oidcClient.GetJSONWebKeysURI()) == 0 { - return errors.WithStack(ErrInvalidRequest.WithHint(`OpenID Connect "request" or "request_uri" context was given, but the OAuth 2.0 Client does not have any JSON Web Keys registered.`)) + return errorsx.WithStack(ErrInvalidRequest.WithHint("OpenID Connect 'request' or 'request_uri' context was given, but the OAuth 2.0 Client does not have any JSON Web Keys registered.")) } assertion := request.Form.Get("request") if location := request.Form.Get("request_uri"); len(location) > 0 { if !stringslice.Has(oidcClient.GetRequestURIs(), location) { - return errors.WithStack(ErrInvalidRequestURI.WithHintf("Request URI \"%s\" is not whitelisted by the OAuth 2.0 Client.", location)) + return errorsx.WithStack(ErrInvalidRequestURI.WithHintf("Request URI '%s' is not whitelisted by the OAuth 2.0 Client.", location)) } hc := f.HTTPClient @@ -75,25 +85,30 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut response, err := hc.Get(location) if err != nil { - return errors.WithStack(ErrInvalidRequestURI.WithHint(`Unable to fetch OpenID Connect request parameters from "request_uri".`).WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because: %s.", err.Error()).WithWrap(err).WithDebug(err.Error())) } defer response.Body.Close() if response.StatusCode != http.StatusOK { - return errors.WithStack(ErrInvalidRequestURI.WithHintf(`Unable to fetch OpenID Connect request parameters from "request_uri" because status code "%d" was expected, but got "%d".`, http.StatusOK, response.StatusCode)) + return errorsx.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because status code '%d' was expected, but got '%d'.", http.StatusOK, response.StatusCode)) } body, err := ioutil.ReadAll(response.Body) if err != nil { - return errors.WithStack(ErrInvalidRequestURI.WithHint(`Unable to fetch OpenID Connect request parameters from "request_uri" because error occurred during body parsing.`).WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequestURI.WithHintf("Unable to fetch OpenID Connect request parameters from 'request_uri' because body parsing failed with: %s.", err).WithWrap(err).WithDebug(err.Error())) } assertion = string(body) } token, err := jwt.ParseWithClaims(assertion, new(jwt.MapClaims), func(t *jwt.Token) (interface{}, error) { - if oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf(`The request object uses signing algorithm %s, but the requested OAuth 2.0 Client enforces signing algorithm %s.`, t.Header["alg"], oidcClient.GetRequestObjectSigningAlgorithm())) + // request_object_signing_alg - OPTIONAL. + // JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request Objects sent to the OP. All Request Objects from this Client MUST be rejected, + // if not signed with this algorithm. Request Objects are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. This algorithm MUST + // be used both when the Request Object is passed by value (using the request parameter) and when it is passed by reference (using the request_uri parameter). + // Servers SHOULD support RS256. The value none MAY be used. The default, if omitted, is that any algorithm supported by the OP and the RP MAY be used. + if oidcClient.GetRequestObjectSigningAlgorithm() != "" && oidcClient.GetRequestObjectSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { + return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf("The request object uses signing algorithm '%s', but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetRequestObjectSigningAlgorithm())) } if t.Method == jwt.SigningMethodNone { @@ -104,23 +119,26 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut case *jwt.SigningMethodRSA: key, err := f.findClientPublicJWK(oidcClient, t, true) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client."), err) } return key, nil case *jwt.SigningMethodECDSA: key, err := f.findClientPublicJWK(oidcClient, t, false) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve ECDSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve ECDSA signing key from OAuth 2.0 Client."), err) } return key, nil case *jwt.SigningMethodRSAPSS: key, err := f.findClientPublicJWK(oidcClient, t, true) if err != nil { - return nil, errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client.").WithCause(err).WithDebug(err.Error())) + return nil, wrapSigningKeyFailure( + ErrInvalidRequestObject.WithHint("Unable to retrieve RSA signing key from OAuth 2.0 Client."), err) } return key, nil default: - return nil, errors.WithStack(ErrInvalidRequestObject.WithHintf(`This request object uses unsupported signing algorithm "%s"."`, t.Header["alg"])) + return nil, errorsx.WithStack(ErrInvalidRequestObject.WithHintf("This request object uses unsupported signing algorithm '%s'.", t.Header["alg"])) } }) if err != nil { @@ -130,16 +148,16 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut if e.Inner != nil { return e.Inner } - return errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to verify the request object's signature.").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequestObject.WithHint("Unable to verify the request object's signature.").WithWrap(err).WithDebug(err.Error())) } return err } else if err := token.Claims.Valid(); err != nil { - return errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequestObject.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithWrap(err).WithDebug(err.Error())) } claims, ok := token.Claims.(*jwt.MapClaims) if !ok { - return errors.WithStack(ErrInvalidRequestObject.WithHint("Unable to type assert claims from request object.").WithDebugf(`Got claims of type %T but expected type "*jwt.MapClaims".`, token.Claims)) + return errorsx.WithStack(ErrInvalidRequestObject.WithHint("Unable to type assert claims from request object.").WithDebugf(`Got claims of type %T but expected type '*jwt.MapClaims'.`, token.Claims)) } for k, v := range *claims { @@ -153,11 +171,12 @@ func (f *Fosite) authorizeRequestParametersFromOpenIDConnectRequest(request *Aut } } + request.State = request.Form.Get("state") request.Form.Set("scope", strings.Join(claimScope, " ")) return nil } -func (f *Fosite) validateAuthorizeRedirectURI(r *http.Request, request *AuthorizeRequest) error { +func (f *Fosite) validateAuthorizeRedirectURI(_ *http.Request, request *AuthorizeRequest) error { // Fetch redirect URI from request rawRedirURI := request.Form.Get("redirect_uri") @@ -166,17 +185,17 @@ func (f *Fosite) validateAuthorizeRedirectURI(r *http.Request, request *Authoriz if err != nil { return err } else if !IsValidRedirectURI(redirectURI) { - return errors.WithStack(ErrInvalidRequest.WithHintf(`The redirect URI "%s" contains an illegal character (for example #) or is otherwise invalid.`, redirectURI)) + return errorsx.WithStack(ErrInvalidRequest.WithHintf("The redirect URI '%s' contains an illegal character (for example #) or is otherwise invalid.", redirectURI)) } request.RedirectURI = redirectURI return nil } -func (f *Fosite) validateAuthorizeScope(r *http.Request, request *AuthorizeRequest) error { +func (f *Fosite) validateAuthorizeScope(_ *http.Request, request *AuthorizeRequest) error { scope := RemoveEmpty(strings.Split(request.Form.Get("scope"), " ")) for _, permission := range scope { if !f.ScopeStrategy(request.Client.GetScopes(), permission) { - return errors.WithStack(ErrInvalidScope.WithHintf(`The OAuth 2.0 Client is not allowed to request scope "%s".`, permission)) + return errorsx.WithStack(ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", permission)) } } request.SetRequestedScopes(scope) @@ -192,7 +211,7 @@ func (f *Fosite) validateResponseTypes(r *http.Request, request *AuthorizeReques // response types is defined by their respective specifications. responseTypes := RemoveEmpty(strings.Split(r.Form.Get("response_type"), " ")) if len(responseTypes) == 0 { - return errors.WithStack(ErrUnsupportedResponseType.WithHint(`The request is missing the "response_type"" parameter.`)) + return errorsx.WithStack(ErrUnsupportedResponseType.WithHint("`The request is missing the 'response_type' parameter.")) } var found bool @@ -204,36 +223,87 @@ func (f *Fosite) validateResponseTypes(r *http.Request, request *AuthorizeReques } if !found { - return errors.WithStack(ErrUnsupportedResponseType.WithHintf("The client is not allowed to request response_type \"%s\".", r.Form.Get("response_type"))) + return errorsx.WithStack(ErrUnsupportedResponseType.WithHintf("The client is not allowed to request response_type '%s'.", r.Form.Get("response_type"))) } request.ResponseTypes = responseTypes return nil } +func (f *Fosite) ParseResponseMode(r *http.Request, request *AuthorizeRequest) error { + switch responseMode := r.Form.Get("response_mode"); responseMode { + case string(ResponseModeDefault): + request.ResponseMode = ResponseModeDefault + case string(ResponseModeFragment): + request.ResponseMode = ResponseModeFragment + case string(ResponseModeQuery): + request.ResponseMode = ResponseModeQuery + case string(ResponseModeFormPost): + request.ResponseMode = ResponseModeFormPost + default: + return errorsx.WithStack(ErrUnsupportedResponseMode.WithHintf("Request with unsupported response_mode \"%s\".", responseMode)) + } + + return nil +} + +func (f *Fosite) validateResponseMode(r *http.Request, request *AuthorizeRequest) error { + if request.ResponseMode == ResponseModeDefault { + return nil + } + + responseModeClient, ok := request.GetClient().(ResponseModeClient) + if !ok { + return errorsx.WithStack(ErrUnsupportedResponseMode.WithHintf("The request has response_mode \"%s\". set but registered OAuth 2.0 client doesn't support response_mode", r.Form.Get("response_mode"))) + } + + var found bool + for _, t := range responseModeClient.GetResponseModes() { + if request.ResponseMode == t { + found = true + break + } + } + + if !found { + return errorsx.WithStack(ErrUnsupportedResponseMode.WithHintf("The client is not allowed to request response_mode '%s'.", r.Form.Get("response_mode"))) + } + + return nil +} + func (f *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (AuthorizeRequester, error) { request := NewAuthorizeRequest() if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { - return request, errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) + return request, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) } - request.Form = r.Form // Save state to the request to be returned in error conditions (https://github.com/ory/hydra/issues/1642) - state := request.Form.Get("state") - request.State = state + request.State = request.Form.Get("state") client, err := f.Store.GetClient(ctx, request.GetRequestForm().Get("client_id")) if err != nil { - return request, errors.WithStack(ErrInvalidClient.WithHint("The requested OAuth 2.0 Client does not exist.").WithCause(err).WithDebug(err.Error())) + return request, errorsx.WithStack(ErrInvalidClient.WithHint("The requested OAuth 2.0 Client does not exist.").WithWrap(err).WithDebug(err.Error())) } request.Client = client + // Now that the base fields (state and client) are populated, we extract all the information + // from the request object or request object uri, if one is set. + // + // All other parse methods should come afterwards so that we ensure that the data is taken + // from the request_object if set. if err := f.authorizeRequestParametersFromOpenIDConnectRequest(request); err != nil { return request, err } + // The request context is now fully available and we can start processing the individual + // fields. + if err := f.ParseResponseMode(r, request); err != nil { + return request, err + } + if err := f.validateAuthorizeRedirectURI(r, request); err != nil { return request, err } @@ -247,22 +317,37 @@ func (f *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (Auth } if len(request.Form.Get("registration")) > 0 { - return request, errors.WithStack(ErrRegistrationNotSupported) + return request, errorsx.WithStack(ErrRegistrationNotSupported) } if err := f.validateResponseTypes(r, request); err != nil { return request, err } + if err := f.validateResponseMode(r, request); err != nil { + return request, err + } + + // A fallback handler to set the default response mode in cases where we can not reach the Authorize Handlers + // but still need the e.g. correct error response mode. + if request.GetResponseMode() == ResponseModeDefault { + if request.ResponseTypes.ExactOne("code") { + request.SetDefaultResponseMode(ResponseModeQuery) + } else { + // If the response type is not `code` it is an implicit/hybrid (fragment) response mode. + request.SetDefaultResponseMode(ResponseModeFragment) + } + } + // rfc6819 4.4.1.8. Threat: CSRF Attack against redirect-uri // The "state" parameter should be used to link the authorization // request with the redirect URI used to deliver the access token (Section 5.3.5). // // https://tools.ietf.org/html/rfc6819#section-4.4.1.8 // The "state" parameter should not be guessable - if len(state) < f.GetMinParameterEntropy() { + if len(request.State) < f.GetMinParameterEntropy() { // We're assuming that using less then, by default, 8 characters for the state can not be considered "unguessable" - return request, errors.WithStack(ErrInvalidState.WithHintf(`Request parameter "state" must be at least be %d characters long to ensure sufficient entropy.`, f.GetMinParameterEntropy())) + return request, errorsx.WithStack(ErrInvalidState.WithHintf("Request parameter 'state' must be at least be %d characters long to ensure sufficient entropy.", f.GetMinParameterEntropy())) } return request, nil diff --git a/authorize_request_handler_oidc_request_test.go b/authorize_request_handler_oidc_request_test.go index 84b8f75cb..2f59670ac 100644 --- a/authorize_request_handler_oidc_request_test.go +++ b/authorize_request_handler_oidc_request_test.go @@ -31,6 +31,8 @@ import ( "net/url" "testing" + "github.com/pkg/errors" + jwt "github.com/dgrijalva/jwt-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,7 +41,9 @@ import ( func mustGenerateAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string { token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - token.Header["kid"] = kid + if kid != "" { + token.Header["kid"] = kid + } tokenString, err := token.SignedString(key) require.NoError(t, err) return tokenString @@ -60,7 +64,6 @@ func mustGenerateNoneAssertion(t *testing.T, claims jwt.MapClaims) string { } func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { - key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { panic(err) @@ -75,8 +78,9 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { }, } - validRequestObject := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}, key, "kid-foo") - validNoneRequestObject := mustGenerateNoneAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}) + validRequestObject := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz", "response_type": "token", "response_mode": "post_form"}, key, "kid-foo") + validRequestObjectWithoutKid := mustGenerateAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz"}, key, "") + validNoneRequestObject := mustGenerateNoneAssertion(t, jwt.MapClaims{"scope": "foo", "foo": "bar", "baz": "baz", "state": "some-state"}) var reqH http.HandlerFunc = func(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte(validRequestObject)) @@ -96,8 +100,9 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { form url.Values d string - expectErr error - expectForm url.Values + expectErr error + expectErrReason string + expectForm url.Values }{ { d: "should pass because no request context given and not openid", @@ -144,24 +149,33 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { expectForm: url.Values{"scope": {"openid"}}, }, { - d: "should fail because kid does not exist", - form: url.Values{"scope": {"openid"}, "request": {mustGenerateAssertion(t, jwt.MapClaims{}, key, "does-not-exists")}}, - client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, - expectErr: ErrInvalidRequestObject, - expectForm: url.Values{"scope": {"openid"}}, + d: "should fail because kid does not exist", + form: url.Values{"scope": {"openid"}, "request": {mustGenerateAssertion(t, jwt.MapClaims{}, key, "does-not-exists")}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + expectErr: ErrInvalidRequestObject, + expectErrReason: "Unable to retrieve RSA signing key from OAuth 2.0 Client. The JSON Web Token uses signing key with kid 'does-not-exists', which could not be found.", + expectForm: url.Values{"scope": {"openid"}}, }, { - d: "should fail because not RS256 token", - form: url.Values{"scope": {"openid"}, "request": {mustGenerateHSAssertion(t, jwt.MapClaims{})}}, - client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, - expectErr: ErrInvalidRequestObject, - expectForm: url.Values{"scope": {"openid"}}, + d: "should fail because not RS256 token", + form: url.Values{"scope": {"openid"}, "request": {mustGenerateHSAssertion(t, jwt.MapClaims{})}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + expectErr: ErrInvalidRequestObject, + expectErrReason: "The request object uses signing algorithm 'HS256', but the requested OAuth 2.0 Client enforces signing algorithm 'RS256'.", + expectForm: url.Values{"scope": {"openid"}}, + }, + { + d: "should pass and set request parameters properly", + form: url.Values{"scope": {"openid"}, "response_type": {"code"}, "response_mode": {"none"}, "request": {validRequestObject}}, + client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, + // The values from form are overwritten by the request object. + expectForm: url.Values{"response_type": {"token"}, "response_mode": {"post_form"}, "scope": {"foo openid"}, "request": {validRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, }, { - d: "should pass and set request parameters properly", - form: url.Values{"scope": {"openid"}, "request": {validRequestObject}}, + d: "should pass even if kid is unset", + form: url.Values{"scope": {"openid"}, "request": {validRequestObjectWithoutKid}}, client: &DefaultOpenIDConnectClient{JSONWebKeys: jwks, RequestObjectSigningAlgorithm: "RS256"}, - expectForm: url.Values{"scope": {"foo openid"}, "request": {validRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, + expectForm: url.Values{"scope": {"foo openid"}, "request": {validRequestObjectWithoutKid}, "foo": {"bar"}, "baz": {"baz"}}, }, { d: "should fail because request uri is not whitelisted", @@ -174,13 +188,19 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { d: "should pass and set request_uri parameters properly and also fetch jwk from remote", form: url.Values{"scope": {"openid"}, "request_uri": {reqTS.URL}}, client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL, RequestObjectSigningAlgorithm: "RS256", RequestURIs: []string{reqTS.URL}}, - expectForm: url.Values{"scope": {"foo openid"}, "request_uri": {reqTS.URL}, "foo": {"bar"}, "baz": {"baz"}}, + expectForm: url.Values{"response_type": {"token"}, "response_mode": {"post_form"}, "scope": {"foo openid"}, "request_uri": {reqTS.URL}, "foo": {"bar"}, "baz": {"baz"}}, }, { d: "should pass when request object uses algorithm none", form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}}, client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL, RequestObjectSigningAlgorithm: "none"}, - expectForm: url.Values{"scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, + expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, + }, + { + d: "should pass when request object uses algorithm none and the client did not explicitly allow any algorithm", + form: url.Values{"scope": {"openid"}, "request": {validNoneRequestObject}}, + client: &DefaultOpenIDConnectClient{JSONWebKeysURI: reqJWK.URL}, + expectForm: url.Values{"state": {"some-state"}, "scope": {"foo openid"}, "request": {validNoneRequestObject}, "foo": {"bar"}, "baz": {"baz"}}, }, } { t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) { @@ -194,6 +214,11 @@ func TestAuthorizeRequestParametersFromOpenIDConnectRequest(t *testing.T) { err := f.authorizeRequestParametersFromOpenIDConnectRequest(req) if tc.expectErr != nil { require.EqualError(t, err, tc.expectErr.Error(), "%+v", err) + if tc.expectErrReason != "" { + real := new(RFC6749Error) + require.True(t, errors.As(err, &real)) + assert.EqualValues(t, real.Reason(), tc.expectErrReason) + } } else { require.NoError(t, err) require.Equal(t, len(tc.expectForm), len(req.Form)) diff --git a/authorize_request_handler_test.go b/authorize_request_handler_test.go index 86afaa6db..b767c6a5f 100644 --- a/authorize_request_handler_test.go +++ b/authorize_request_handler_test.go @@ -44,9 +44,7 @@ import ( // If a Response Type contains one of more space characters (%20), it is compared as a space-delimited list of // values in which the order of values does not matter. func TestNewAuthorizeRequest(t *testing.T) { - ctrl := gomock.NewController(t) - store := NewMockStorage(ctrl) - defer ctrl.Finish() + var store *MockStorage redir, _ := url.Parse("https://foo.bar/cb") specialCharRedir, _ := url.Parse("web+application://callback") @@ -227,6 +225,76 @@ func TestNewAuthorizeRequest(t *testing.T) { }, }, }, + /* repeated audience parameter */ + { + desc: "repeated audience parameter", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "audience": {"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{ + ResponseTypes: []string{"code token"}, + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, nil) + }, + expect: &AuthorizeRequest{ + RedirectURI: redir, + ResponseTypes: []string{"code", "token"}, + State: "strong-state", + Request: Request{ + Client: &DefaultClient{ + ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + RequestedScope: []string{"foo", "bar"}, + RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + }, + }, + /* repeated audience parameter with tricky values */ + { + desc: "repeated audience parameter with tricky values", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: ExactAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "audience": {"test value", ""}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{ + ResponseTypes: []string{"code token"}, + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + Audience: []string{"test value"}, + }, nil) + }, + expect: &AuthorizeRequest{ + RedirectURI: redir, + ResponseTypes: []string{"code", "token"}, + State: "strong-state", + Request: Request{ + Client: &DefaultClient{ + ResponseTypes: []string{"code token"}, RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + Audience: []string{"test value"}, + }, + RequestedScope: []string{"foo", "bar"}, + RequestedAudience: []string{"test value"}, + }, + }, + }, /* redirect_uri with special character in protocol*/ { desc: "redirect_uri with special character", @@ -297,8 +365,197 @@ func TestNewAuthorizeRequest(t *testing.T) { }, }, }, + /* fails because unknown response_mode*/ + { + desc: "should fail because unknown response_mode", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "response_mode": {"unknown"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"}, ResponseTypes: []string{"code token"}}, nil) + }, + expectedError: ErrUnsupportedResponseMode, + }, + /* fails because response_mode is requested but the OAuth 2.0 client doesn't support response mode */ + { + desc: "should fail because response_mode is requested but the OAuth 2.0 client doesn't support response mode", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "response_mode": {"form_post"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}, Scopes: []string{"foo", "bar"}, ResponseTypes: []string{"code token"}}, nil) + }, + expectedError: ErrUnsupportedResponseMode, + }, + /* fails because requested response mode is not allowed */ + { + desc: "should fail because requested response mode is not allowed", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "response_mode": {"form_post"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code token"}, + }, + ResponseModes: []ResponseModeType{ResponseModeQuery}, + }, nil) + }, + expectedError: ErrUnsupportedResponseMode, + }, + /* success with response mode */ + { + desc: "success with response mode", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "response_mode": {"form_post"}, + "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code token"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeFormPost}, + }, nil) + }, + expect: &AuthorizeRequest{ + RedirectURI: redir, + ResponseTypes: []string{"code", "token"}, + State: "strong-state", + Request: Request{ + Client: &DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code token"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeFormPost}, + }, + RequestedScope: []string{"foo", "bar"}, + RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + }, + }, + /* determine correct response mode if default */ + { + desc: "success with response mode", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeQuery}, + }, nil) + }, + expect: &AuthorizeRequest{ + RedirectURI: redir, + ResponseTypes: []string{"code"}, + State: "strong-state", + Request: Request{ + Client: &DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeQuery}, + }, + RequestedScope: []string{"foo", "bar"}, + RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + }, + }, + /* determine correct response mode if default */ + { + desc: "success with response mode", + conf: &Fosite{Store: store, ScopeStrategy: ExactScopeStrategy, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}, + query: url.Values{ + "redirect_uri": {"https://foo.bar/cb"}, + "client_id": {"1234"}, + "response_type": {"code token"}, + "state": {"strong-state"}, + "scope": {"foo bar"}, + "audience": {"https://cloud.ory.sh/api https://www.ory.sh/api"}, + }, + mock: func() { + store.EXPECT().GetClient(gomock.Any(), "1234").Return(&DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code token"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeFragment}, + }, nil) + }, + expect: &AuthorizeRequest{ + RedirectURI: redir, + ResponseTypes: []string{"code", "token"}, + State: "strong-state", + Request: Request{ + Client: &DefaultResponseModeClient{ + DefaultClient: &DefaultClient{ + RedirectURIs: []string{"https://foo.bar/cb"}, + Scopes: []string{"foo", "bar"}, + ResponseTypes: []string{"code token"}, + Audience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + ResponseModes: []ResponseModeType{ResponseModeFragment}, + }, + RequestedScope: []string{"foo", "bar"}, + RequestedAudience: []string{"https://cloud.ory.sh/api", "https://www.ory.sh/api"}, + }, + }, + }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + ctrl := gomock.NewController(t) + store = NewMockStorage(ctrl) + defer ctrl.Finish() + c.mock() if c.r == nil { c.r = &http.Request{Header: http.Header{}} @@ -307,6 +564,7 @@ func TestNewAuthorizeRequest(t *testing.T) { } } + c.conf.Store = store ar, err := c.conf.NewAuthorizeRequest(context.Background(), c.r) if c.expectedError != nil { assert.EqualError(t, err, c.expectedError.Error()) diff --git a/authorize_response.go b/authorize_response.go index c7feac3f5..60edb83e7 100644 --- a/authorize_response.go +++ b/authorize_response.go @@ -28,17 +28,15 @@ import ( // AuthorizeResponse is an implementation of AuthorizeResponder type AuthorizeResponse struct { - Header http.Header - Query url.Values - Fragment url.Values - code string + Header http.Header + Parameters url.Values + code string } func NewAuthorizeResponse() *AuthorizeResponse { return &AuthorizeResponse{ - Header: http.Header{}, - Query: url.Values{}, - Fragment: url.Values{}, + Header: http.Header{}, + Parameters: url.Values{}, } } @@ -54,24 +52,13 @@ func (a *AuthorizeResponse) AddHeader(key, value string) { a.Header.Add(key, value) } -func (a *AuthorizeResponse) GetQuery() url.Values { - return a.Query +func (a *AuthorizeResponse) GetParameters() url.Values { + return a.Parameters } -func (a *AuthorizeResponse) GetFragment() url.Values { - return a.Fragment -} - -func (a *AuthorizeResponse) AddQuery(key, value string) { - if key == "code" { - a.code = value - } - a.Query.Add(key, value) -} - -func (a *AuthorizeResponse) AddFragment(key, value string) { +func (a *AuthorizeResponse) AddParameter(key, value string) { if key == "code" { a.code = value } - a.Fragment.Add(key, value) + a.Parameters.Add(key, value) } diff --git a/authorize_response_test.go b/authorize_response_test.go index bd3d95ae0..c3a684e7f 100644 --- a/authorize_response_test.go +++ b/authorize_response_test.go @@ -29,16 +29,15 @@ import ( func TestAuthorizeResponse(t *testing.T) { ar := NewAuthorizeResponse() - ar.AddFragment("foo", "bar") - ar.AddQuery("foo", "baz") + ar.AddParameter("foo", "bar") + ar.AddParameter("bar", "bar") + ar.AddHeader("foo", "foo") - ar.AddFragment("code", "bar") + ar.AddParameter("code", "bar") assert.Equal(t, "bar", ar.GetCode()) - ar.AddQuery("code", "baz") - assert.Equal(t, "baz", ar.GetCode()) - assert.Equal(t, "bar", ar.GetFragment().Get("foo")) - assert.Equal(t, "baz", ar.GetQuery().Get("foo")) + assert.Equal(t, "bar", ar.GetParameters().Get("foo")) assert.Equal(t, "foo", ar.GetHeader().Get("foo")) + assert.Equal(t, "bar", ar.GetParameters().Get("bar")) } diff --git a/authorize_response_writer.go b/authorize_response_writer.go index 208b104c2..4422c63aa 100644 --- a/authorize_response_writer.go +++ b/authorize_response_writer.go @@ -26,14 +26,13 @@ import ( "net/http" "net/url" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" ) func (f *Fosite) NewAuthorizeResponse(ctx context.Context, ar AuthorizeRequester, session Session) (AuthorizeResponder, error) { var resp = &AuthorizeResponse{ - Header: http.Header{}, - Query: url.Values{}, - Fragment: url.Values{}, + Header: http.Header{}, + Parameters: url.Values{}, } ar.SetSession(session) @@ -44,7 +43,11 @@ func (f *Fosite) NewAuthorizeResponse(ctx context.Context, ar AuthorizeRequester } if !ar.DidHandleAllResponseTypes() { - return nil, errors.WithStack(ErrUnsupportedResponseType) + return nil, errorsx.WithStack(ErrUnsupportedResponseType) + } + + if ar.GetDefaultResponseMode() == ResponseModeFragment && ar.GetResponseMode() == ResponseModeQuery { + return nil, ErrUnsupportedResponseMode.WithHintf("Insecure response_mode '%s' for the response_type '%s'.", ar.GetResponseMode(), ar.GetResponseTypes()) } return resp, nil diff --git a/authorize_response_writer_test.go b/authorize_response_writer_test.go index 12961c47a..650e3fa29 100644 --- a/authorize_response_writer_test.go +++ b/authorize_response_writer_test.go @@ -64,6 +64,8 @@ func TestNewAuthorizeResponse(t *testing.T) { mock: func() { handlers[0].EXPECT().HandleAuthorizeEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) ar.EXPECT().DidHandleAllResponseTypes().Return(true) + ar.EXPECT().GetDefaultResponseMode().Return(ResponseModeFragment) + ar.EXPECT().GetResponseMode().Return(ResponseModeDefault) }, isErr: false, }, @@ -73,6 +75,8 @@ func TestNewAuthorizeResponse(t *testing.T) { handlers[0].EXPECT().HandleAuthorizeEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) handlers[0].EXPECT().HandleAuthorizeEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) ar.EXPECT().DidHandleAllResponseTypes().Return(true) + ar.EXPECT().GetDefaultResponseMode().Return(ResponseModeFragment) + ar.EXPECT().GetResponseMode().Return(ResponseModeDefault) }, isErr: false, }, @@ -85,6 +89,19 @@ func TestNewAuthorizeResponse(t *testing.T) { isErr: true, expectErr: fooErr, }, + { + mock: func() { + oauth2 = duo + handlers[0].EXPECT().HandleAuthorizeEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + handlers[0].EXPECT().HandleAuthorizeEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + ar.EXPECT().DidHandleAllResponseTypes().Return(true) + ar.EXPECT().GetDefaultResponseMode().Return(ResponseModeFragment) + ar.EXPECT().GetResponseMode().Return(ResponseModeQuery).Times(2) + ar.EXPECT().GetResponseTypes().Return([]string{"token", "code"}) + }, + isErr: true, + expectErr: ErrUnsupportedResponseMode.WithHintf("Insecure response_mode '%s' for the response_type '%s'.", ResponseModeQuery, []string{"token", "code"}), + }, } { c.mock() responder, err := oauth2.NewAuthorizeResponse(ctx, ar, new(DefaultSession)) diff --git a/authorize_write.go b/authorize_write.go index 9556a35c1..cb7526fa7 100644 --- a/authorize_write.go +++ b/authorize_write.go @@ -23,25 +23,9 @@ package fosite import ( "net/http" - "regexp" -) - -var ( - // scopeMatch = regexp.MustCompile("scope=[^\\&]+.*$") - plusMatch = regexp.MustCompile("\\+") ) func (f *Fosite) WriteAuthorizeResponse(rw http.ResponseWriter, ar AuthorizeRequester, resp AuthorizeResponder) { - redir := ar.GetRedirectURI() - - // Explicit grants - q := redir.Query() - rq := resp.GetQuery() - for k := range rq { - q.Set(k, rq.Get(k)) - } - redir.RawQuery = q.Encode() - // Set custom headers, e.g. "X-MySuperCoolCustomHeader" or "X-DONT-CACHE-ME"... wh := rw.Header() rh := resp.GetHeader() @@ -49,27 +33,42 @@ func (f *Fosite) WriteAuthorizeResponse(rw http.ResponseWriter, ar AuthorizeRequ wh.Set(k, rh.Get(k)) } - // Implicit grants - // The endpoint URI MUST NOT include a fragment component. - redir.Fragment = "" - - u := redir.String() - - fr := resp.GetFragment() - if len(fr) > 0 { - u = u + "#" + fr.Encode() - } - - u = plusMatch.ReplaceAllString(u, "%20") - wh.Set("Cache-Control", "no-store") wh.Set("Pragma", "no-cache") - // https://tools.ietf.org/html/rfc6749#section-4.1.1 - // When a decision is established, the authorization server directs the - // user-agent to the provided client redirection URI using an HTTP - // redirection response, or by other means available to it via the - // user-agent. - wh.Set("Location", u) + redir := ar.GetRedirectURI() + switch ar.GetResponseMode() { + case ResponseModeFormPost: + //form_post + rw.Header().Add("Content-Type", "text/html;charset=UTF-8") + WriteAuthorizeFormPostResponse(redir.String(), resp.GetParameters(), GetPostFormHTMLTemplate(*f), rw) + return + case ResponseModeQuery, ResponseModeDefault: + // Explicit grants + q := redir.Query() + rq := resp.GetParameters() + for k := range rq { + q.Set(k, rq.Get(k)) + } + redir.RawQuery = q.Encode() + sendRedirect(redir.String(), rw) + return + case ResponseModeFragment: + // Implicit grants + // The endpoint URI MUST NOT include a fragment component. + redir.Fragment = "" + URLSetFragment(redir, resp.GetParameters()) + sendRedirect(redir.String(), rw) + return + } +} + +// https://tools.ietf.org/html/rfc6749#section-4.1.1 +// When a decision is established, the authorization server directs the +// user-agent to the provided client redirection URI using an HTTP +// redirection response, or by other means available to it via the +// user-agent. +func sendRedirect(url string, rw http.ResponseWriter) { + rw.Header().Set("Location", url) rw.WriteHeader(http.StatusFound) } diff --git a/authorize_write_test.go b/authorize_write_test.go index dd4d12a20..3d92d3dec 100644 --- a/authorize_write_test.go +++ b/authorize_write_test.go @@ -50,11 +50,11 @@ func TestWriteAuthorizeResponse(t *testing.T) { setup: func() { redir, _ := url.Parse("https://foobar.com/?foo=bar") ar.EXPECT().GetRedirectURI().Return(redir) - resp.EXPECT().GetFragment().Return(url.Values{}) + ar.EXPECT().GetResponseMode().Return(ResponseModeDefault) + resp.EXPECT().GetParameters().Return(url.Values{}) resp.EXPECT().GetHeader().Return(http.Header{}) - resp.EXPECT().GetQuery().Return(url.Values{}) - rw.EXPECT().Header().Return(header) + rw.EXPECT().Header().Return(header).Times(2) rw.EXPECT().WriteHeader(http.StatusFound) }, expect: func() { @@ -69,11 +69,11 @@ func TestWriteAuthorizeResponse(t *testing.T) { setup: func() { redir, _ := url.Parse("https://foobar.com/?foo=bar") ar.EXPECT().GetRedirectURI().Return(redir) - resp.EXPECT().GetFragment().Return(url.Values{"bar": {"baz"}}) + ar.EXPECT().GetResponseMode().Return(ResponseModeFragment) + resp.EXPECT().GetParameters().Return(url.Values{"bar": {"baz"}}) resp.EXPECT().GetHeader().Return(http.Header{}) - resp.EXPECT().GetQuery().Return(url.Values{}) - rw.EXPECT().Header().Return(header) + rw.EXPECT().Header().Return(header).Times(2) rw.EXPECT().WriteHeader(http.StatusFound) }, expect: func() { @@ -88,36 +88,37 @@ func TestWriteAuthorizeResponse(t *testing.T) { setup: func() { redir, _ := url.Parse("https://foobar.com/?foo=bar") ar.EXPECT().GetRedirectURI().Return(redir) - resp.EXPECT().GetFragment().Return(url.Values{"bar": {"baz"}}) + ar.EXPECT().GetResponseMode().Return(ResponseModeQuery) + resp.EXPECT().GetParameters().Return(url.Values{"bar": {"baz"}}) resp.EXPECT().GetHeader().Return(http.Header{}) - resp.EXPECT().GetQuery().Return(url.Values{"bar": {"baz"}}) - rw.EXPECT().Header().Return(header) + rw.EXPECT().Header().Return(header).Times(2) rw.EXPECT().WriteHeader(http.StatusFound) }, expect: func() { - assert.Equal(t, http.Header{ - "Location": []string{"https://foobar.com/?bar=baz&foo=bar#bar=baz"}, - "Cache-Control": []string{"no-store"}, - "Pragma": []string{"no-cache"}, - }, header) + expectedUrl, _ := url.Parse("https://foobar.com/?foo=bar&bar=baz") + actualUrl, err := url.Parse(header.Get("Location")) + assert.Nil(t, err) + assert.Equal(t, expectedUrl.Query(), actualUrl.Query()) + assert.Equal(t, "no-cache", header.Get("Pragma")) + assert.Equal(t, "no-store", header.Get("Cache-Control")) }, }, { setup: func() { redir, _ := url.Parse("https://foobar.com/?foo=bar") ar.EXPECT().GetRedirectURI().Return(redir) - resp.EXPECT().GetFragment().Return(url.Values{"bar": {"baz"}, "scope": {"a b"}}) + ar.EXPECT().GetResponseMode().Return(ResponseModeFragment) + resp.EXPECT().GetParameters().Return(url.Values{"bar": {"b+az ab"}}) resp.EXPECT().GetHeader().Return(http.Header{"X-Bar": {"baz"}}) - resp.EXPECT().GetQuery().Return(url.Values{"bar": {"b+az"}, "scope": {"a b"}}) - rw.EXPECT().Header().Return(header) + rw.EXPECT().Header().Return(header).Times(2) rw.EXPECT().WriteHeader(http.StatusFound) }, expect: func() { assert.Equal(t, http.Header{ "X-Bar": {"baz"}, - "Location": {"https://foobar.com/?bar=b%2Baz&foo=bar&scope=a%20b#bar=baz&scope=a%20b"}, + "Location": {"https://foobar.com/?foo=bar#bar=b+az%20ab"}, "Cache-Control": []string{"no-store"}, "Pragma": []string{"no-cache"}, }, header) @@ -127,22 +128,59 @@ func TestWriteAuthorizeResponse(t *testing.T) { setup: func() { redir, _ := url.Parse("https://foobar.com/?foo=bar") ar.EXPECT().GetRedirectURI().Return(redir) - resp.EXPECT().GetFragment().Return(url.Values{"bar": {"baz"}, "scope": {"api:*"}}) + ar.EXPECT().GetResponseMode().Return(ResponseModeQuery) + resp.EXPECT().GetParameters().Return(url.Values{"bar": {"b+az"}, "scope": {"a b"}}) resp.EXPECT().GetHeader().Return(http.Header{"X-Bar": {"baz"}}) - resp.EXPECT().GetQuery().Return(url.Values{"bar": {"b+az"}, "scope": {"api:*"}}) - rw.EXPECT().Header().Return(header) + rw.EXPECT().Header().Return(header).Times(2) + rw.EXPECT().WriteHeader(http.StatusFound) + }, + expect: func() { + expectedUrl, err := url.Parse("https://foobar.com/?foo=bar&bar=b%2Baz&scope=a+b") + assert.Nil(t, err) + actualUrl, err := url.Parse(header.Get("Location")) + assert.Nil(t, err) + assert.Equal(t, expectedUrl.Query(), actualUrl.Query()) + assert.Equal(t, "no-cache", header.Get("Pragma")) + assert.Equal(t, "no-store", header.Get("Cache-Control")) + assert.Equal(t, "baz", header.Get("X-Bar")) + }, + }, + { + setup: func() { + redir, _ := url.Parse("https://foobar.com/?foo=bar") + ar.EXPECT().GetRedirectURI().Return(redir) + ar.EXPECT().GetResponseMode().Return(ResponseModeFragment) + resp.EXPECT().GetParameters().Return(url.Values{"scope": {"api:*"}}) + resp.EXPECT().GetHeader().Return(http.Header{"X-Bar": {"baz"}}) + + rw.EXPECT().Header().Return(header).Times(2) rw.EXPECT().WriteHeader(http.StatusFound) }, expect: func() { assert.Equal(t, http.Header{ "X-Bar": {"baz"}, - "Location": {"https://foobar.com/?bar=b%2Baz&foo=bar&scope=api%3A%2A#bar=baz&scope=api%3A%2A"}, + "Location": {"https://foobar.com/?foo=bar#scope=api:*"}, "Cache-Control": []string{"no-store"}, "Pragma": []string{"no-cache"}, }, header) }, }, + { + setup: func() { + redir, _ := url.Parse("https://foobar.com/?foo=bar") + ar.EXPECT().GetRedirectURI().Return(redir) + ar.EXPECT().GetResponseMode().Return(ResponseModeFormPost) + resp.EXPECT().GetHeader().Return(http.Header{"X-Bar": {"baz"}}) + resp.EXPECT().GetParameters().Return(url.Values{"code": {"poz65kqoneu"}, "state": {"qm6dnsrn"}}) + + rw.EXPECT().Header().Return(header).AnyTimes() + rw.EXPECT().Write(gomock.Any()).AnyTimes() + }, + expect: func() { + assert.Equal(t, "text/html;charset=UTF-8", header.Get("Content-Type")) + }, + }, } { t.Logf("Starting test case %d", k) c.setup() diff --git a/client.go b/client.go index 9a4ee32e1..c7cf6c5c6 100644 --- a/client.go +++ b/client.go @@ -80,6 +80,12 @@ type OpenIDConnectClient interface { GetTokenEndpointAuthSigningAlgorithm() string } +// ResponseModeClient represents a client capable of handling response_mode +type ResponseModeClient interface { + // GetResponseMode returns the response modes that client is allowed to send + GetResponseModes() []ResponseModeType +} + // DefaultClient is a simple default implementation of the Client interface. type DefaultClient struct { ID string `json:"id"` @@ -102,6 +108,11 @@ type DefaultOpenIDConnectClient struct { TokenEndpointAuthSigningAlgorithm string `json:"token_endpoint_auth_signing_alg"` } +type DefaultResponseModeClient struct { + *DefaultClient + ResponseModes []ResponseModeType `json:"response_modes"` +} + func (c *DefaultClient) GetID() string { return c.ID } @@ -177,3 +188,7 @@ func (c *DefaultOpenIDConnectClient) GetTokenEndpointAuthMethod() string { func (c *DefaultOpenIDConnectClient) GetRequestURIs() []string { return c.RequestURIs } + +func (c *DefaultResponseModeClient) GetResponseModes() []ResponseModeType { + return c.ResponseModes +} diff --git a/client_authentication.go b/client_authentication.go index ca3d3553c..41cb27b66 100644 --- a/client_authentication.go +++ b/client_authentication.go @@ -31,6 +31,8 @@ import ( "net/url" "time" + "github.com/ory/x/errorsx" + jwt "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" jose "gopkg.in/square/go-jose.v2" @@ -61,14 +63,14 @@ func (f *Fosite) findClientPublicJWK(oidcClient OpenIDConnectClient, t *jwt.Toke return findPublicKey(t, keys, expectsRSAKey) } - return nil, errors.WithStack(ErrInvalidClient.WithHint("The OAuth 2.0 Client has no JSON Web Keys set registered, but they are needed to complete the request.")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("The OAuth 2.0 Client has no JSON Web Keys set registered, but they are needed to complete the request.")) } func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values) (Client, error) { if assertionType := form.Get("client_assertion_type"); assertionType == clientAssertionJWTBearerType { assertion := form.Get("client_assertion") if len(assertion) == 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of \"%s\".", clientAssertionJWTBearerType)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of '%s'.", clientAssertionJWTBearerType)) } var clientID string @@ -83,9 +85,9 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if clientID == "" { if claims, ok := t.Claims.(*jwt.MapClaims); !ok { - return nil, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to type assert claims from client_assertion.").WithDebugf(`Expected claims to be of type "*jwt.MapClaims" but got "%T".`, t.Claims)) + return nil, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to type assert claims from client_assertion.").WithDebugf(`Expected claims to be of type '*jwt.MapClaims' but got '%T'.`, t.Claims)) } else if sub, ok := (*claims)["sub"].(string); !ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint(`The claim "sub" from the client_assertion JSON Web Token is undefined.`)) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("The claim 'sub' from the client_assertion JSON Web Token is undefined.")) } else { clientID = sub } @@ -93,31 +95,31 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u client, err = f.Store.GetClient(ctx, clientID) if err != nil { - return nil, errors.WithStack(ErrInvalidClient.WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error())) } oidcClient, ok := client.(OpenIDConnectClient) if !ok { - return nil, errors.WithStack(ErrInvalidRequest.WithHint("The server configuration does not support OpenID Connect specific authentication methods.")) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHint("The server configuration does not support OpenID Connect specific authentication methods.")) } switch oidcClient.GetTokenEndpointAuthMethod() { case "private_key_jwt": break case "none": - return nil, errors.WithStack(ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however \"client_assertion\" was provided in the request.")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however 'client_assertion' was provided in the request.")) case "client_secret_post": fallthrough case "client_secret_basic": - return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method \"%s\", however \"client_assertion\" was provided in the request.", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however 'client_assertion' was provided in the request.", oidcClient.GetTokenEndpointAuthMethod())) case "client_secret_jwt": fallthrough default: - return nil, errors.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method \"%s\", however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod())) } if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) { - return nil, errors.WithStack(ErrInvalidClient.WithHintf(`The "client_assertion" uses signing algorithm "%s", but the requested OAuth 2.0 Client enforces signing algorithm "%s".`, t.Header["alg"], oidcClient.GetTokenEndpointAuthSigningAlgorithm())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetTokenEndpointAuthSigningAlgorithm())) } if _, ok := t.Method.(*jwt.SigningMethodRSA); ok { @@ -127,10 +129,10 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } else if _, ok := t.Method.(*jwt.SigningMethodRSAPSS); ok { return f.findClientPublicJWK(oidcClient, t, true) } else if _, ok := t.Method.(*jwt.SigningMethodHMAC); ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint("This authorization server does not support client authentication method \"client_secret_jwt\".")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("This authorization server does not support client authentication method 'client_secret_jwt'.")) } - return nil, errors.WithStack(ErrInvalidClient.WithHintf(`The "client_assertion" request parameter uses unsupported signing algorithm "%s".`, t.Header["alg"])) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header["alg"])) }) if err != nil { // Do not re-process already enhanced errors @@ -139,29 +141,29 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if e.Inner != nil { return nil, e.Inner } - return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to verify the integrity of the \"client_assertion\" value.").WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Unable to verify the integrity of the 'client_assertion' value.").WithWrap(err).WithDebug(err.Error())) } return nil, err } else if err := token.Claims.Valid(); err != nil { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithWrap(err).WithDebug(err.Error())) } claims, ok := token.Claims.(*jwt.MapClaims) if !ok { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to type assert claims from request parameter \"client_assertion\".").WithDebugf(`Got claims of type %T but expected type "*jwt.MapClaims".`, token.Claims)) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Unable to type assert claims from request parameter 'client_assertion'.").WithDebugf("Got claims of type %T but expected type '*jwt.MapClaims'.", token.Claims)) } var jti string if !claims.VerifyIssuer(clientID, true) { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"iss\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client.")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'iss' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) } else if f.TokenURL == "" { - return nil, errors.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set.")) + return nil, errorsx.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set.")) } else if sub, ok := (*claims)["sub"].(string); !ok || sub != clientID { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"sub\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client.")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'sub' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client.")) } else if jti, ok = (*claims)["jti"].(string); !ok || len(jti) == 0 { - return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"jti\" from \"client_assertion\" must be set but is not.")) + return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'jti' from 'client_assertion' must be set but is not.")) } else if f.Store.ClientAssertionJWTValid(context.Background(), jti) != nil { - return nil, errors.WithStack(ErrJTIKnown.WithHint("Claim \"jti\" from \"client_assertion\" MUST only be used once.")) + return nil, errorsx.WithStack(ErrJTIKnown.WithHint("Claim 'jti' from 'client_assertion' MUST only be used once.")) } // type conversion according to jwt.MapClaims.VerifyExpiresAt @@ -177,7 +179,7 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } if err != nil { - return nil, errors.WithStack(err) + return nil, errorsx.WithStack(err) } if err := f.Store.SetClientAssertionJWT(context.Background(), jti, time.Unix(expiry, 0)); err != nil { return nil, err @@ -185,7 +187,7 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u if auds, ok := (*claims)["aud"].([]interface{}); !ok { if !claims.VerifyAudience(f.TokenURL, true) { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim \"audience\" from \"client_assertion\" must match the authorization server's token endpoint \"%s\".", f.TokenURL)) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL)) } } else { var found bool @@ -197,13 +199,13 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u } if !found { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("Claim \"audience\" from \"client_assertion\" must match the authorization server's token endpoint \"%s\".", f.TokenURL)) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL)) } } return client, nil } else if len(assertionType) > 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unknown client_assertion_type \"%s\".", assertionType)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unknown client_assertion_type '%s'.", assertionType)) } clientID, clientSecret, err := clientCredentialsFromRequest(r, form) @@ -213,17 +215,17 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u client, err := f.Store.GetClient(ctx, clientID) if err != nil { - return nil, errors.WithStack(ErrInvalidClient.WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error())) } if oidcClient, ok := client.(OpenIDConnectClient); !ok { // If this isn't an OpenID Connect client then we actually don't care about any of this, just continue! } else if ok && form.Get("client_id") != "" && form.Get("client_secret") != "" && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_post" { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"client_secret_post\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"client_secret_post\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.", oidcClient.GetTokenEndpointAuthMethod())) } else if _, _, basicOk := r.BasicAuth(); basicOk && ok && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_basic" { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"client_secret_basic\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"client_secret_basic\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_basic' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_basic'.", oidcClient.GetTokenEndpointAuthMethod())) } else if ok && oidcClient.GetTokenEndpointAuthMethod() != "none" && client.IsPublic() { - return nil, errors.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method \"%s\", but method \"none\" was requested. You must configure the OAuth 2.0 client's \"token_endpoint_auth_method\" value to accept \"none\".", oidcClient.GetTokenEndpointAuthMethod())) + return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'none' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'none'.", oidcClient.GetTokenEndpointAuthMethod())) } if client.IsPublic() { @@ -232,21 +234,25 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u // Enforce client authentication if err := f.Hasher.Compare(ctx, client.GetHashedSecret(), []byte(clientSecret)); err != nil { - return nil, errors.WithStack(ErrInvalidClient.WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error())) } return client, nil } func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (interface{}, error) { + keys := set.Keys + if len(keys) == 0 { + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The retrieved JSON Web Key Set does not contain any keys.")) + } + kid, ok := t.Header["kid"].(string) - if !ok { - return nil, errors.WithStack(ErrInvalidRequest.WithHint("The JSON Web Token must contain a kid header value but did not.")) + if ok { + keys = set.Key(kid) } - keys := set.Key(kid) if len(keys) == 0 { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid \"%s\", which could not be found.", kid)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid '%s', which could not be found.", kid)) } for _, key := range keys { @@ -265,9 +271,9 @@ func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (i } if expectsRSAKey { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find RSA public key with use=\"sig\" for kid \"%s\" in JSON Web Key Set.", kid)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to find RSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid)) } else { - return nil, errors.WithStack(ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use=\"sig\" for kid \"%s\" in JSON Web Key Set.", kid)) + return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid)) } } @@ -275,9 +281,9 @@ func clientCredentialsFromRequest(r *http.Request, form url.Values) (clientID, c if id, secret, ok := r.BasicAuth(); !ok { return clientCredentialsFromRequestBody(form, true) } else if clientID, err = url.QueryUnescape(id); err != nil { - return "", "", errors.WithStack(ErrInvalidRequest.WithHint(`The client id in the HTTP authorization header could not be decoded from "application/x-www-form-urlencoded".`).WithCause(err).WithDebug(err.Error())) + return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("The client id in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error())) } else if clientSecret, err = url.QueryUnescape(secret); err != nil { - return "", "", errors.WithStack(ErrInvalidRequest.WithHint(`The client secret in the HTTP authorization header could not be decoded from "application/x-www-form-urlencoded".`).WithCause(err).WithDebug(err.Error())) + return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("The client secret in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error())) } return clientID, clientSecret, nil @@ -288,7 +294,7 @@ func clientCredentialsFromRequestBody(form url.Values, forceID bool) (clientID, clientSecret = form.Get("client_secret") if clientID == "" && forceID { - return "", "", errors.WithStack(ErrInvalidRequest.WithHint("Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body.")) + return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body.")) } return clientID, clientSecret, nil diff --git a/client_authentication_jwks_strategy.go b/client_authentication_jwks_strategy.go index 9d54ca0e7..4c6733fb6 100644 --- a/client_authentication_jwks_strategy.go +++ b/client_authentication_jwks_strategy.go @@ -26,7 +26,8 @@ import ( "net/http" "sync" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" + jose "gopkg.in/square/go-jose.v2" ) @@ -60,17 +61,17 @@ func (s *DefaultJWKSFetcherStrategy) Resolve(location string, forceRefresh bool) if !ok || forceRefresh { response, err := s.client.Get(location) if err != nil { - return nil, errors.WithStack(ErrServerError.WithHintf(`Unable to fetch JSON Web Keys from location "%s".`, location).WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrServerError.WithHintf("Unable to fetch JSON Web Keys from location '%s'. Check for typos or other network issues.", location).WithWrap(err).WithDebug(err.Error())) } defer response.Body.Close() if response.StatusCode < 200 || response.StatusCode >= 400 { - return nil, errors.WithStack(ErrServerError.WithHintf(`Expected successful status code from location "%s", but received code "%d".`, location, response.StatusCode)) + return nil, errorsx.WithStack(ErrServerError.WithHintf("Expected successful status code in range of 200 - 399 from location '%s' but received code %d.", location, response.StatusCode)) } var set jose.JSONWebKeySet if err := json.NewDecoder(response.Body).Decode(&set); err != nil { - return nil, errors.WithStack(ErrServerError.WithHintf("Unable to decode JSON Web Keys from location \"%s\".", location).WithCause(err).WithDebug(err.Error())) + return nil, errorsx.WithStack(ErrServerError.WithHintf("Unable to decode JSON Web Keys from location '%s'. Please check for typos and if the URL returns valid JSON.", location).WithWrap(err).WithDebug(err.Error())) } s.keys[location] = set diff --git a/client_authentication_test.go b/client_authentication_test.go index 16b08bbea..bde8a4819 100644 --- a/client_authentication_test.go +++ b/client_authentication_test.go @@ -502,7 +502,7 @@ func TestAuthenticateClient(t *testing.T) { if errors.As(err, &validationError) { t.Logf("Error is: %s", validationError.Inner) } else if errors.As(err, &rfcError) { - t.Logf("Debug is: %s", rfcError.Debug) + t.Logf("DebugField is: %s", rfcError.DebugField) } } require.NoError(t, err) diff --git a/client_test.go b/client_test.go index e88d70dfa..baf07d588 100644 --- a/client_test.go +++ b/client_test.go @@ -49,3 +49,8 @@ func TestDefaultClient(t *testing.T) { assert.Equal(t, "code", sc.GetResponseTypes()[0]) assert.Equal(t, "authorization_code", sc.GetGrantTypes()[0]) } + +func TestDefaultResponseModeClient_GetResponseMode(t *testing.T) { + rc := &DefaultResponseModeClient{ResponseModes: []ResponseModeType{ResponseModeFragment}} + assert.Equal(t, []ResponseModeType{ResponseModeFragment}, rc.GetResponseModes()) +} diff --git a/compose/compose.go b/compose/compose.go index 53835e21a..03e58b87b 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -70,6 +70,7 @@ func Compose(config *Config, storage interface{}, strategy interface{}, hasher f TokenURL: config.TokenURL, JWKSFetcherStrategy: config.GetJWKSFetcherStrategy(), MinParameterEntropy: config.GetMinParameterEntropy(), + UseLegacyErrorFormat: config.UseLegacyErrorFormat, } for _, factory := range factories { diff --git a/compose/compose_oauth2.go b/compose/compose_oauth2.go index 5cb4aae14..5ad43bf6d 100644 --- a/compose/compose_oauth2.go +++ b/compose/compose_oauth2.go @@ -23,6 +23,7 @@ package compose import ( "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/token/jwt" ) // OAuth2AuthorizeExplicitFactory creates an OAuth2 authorize code grant ("authorize explicit flow") handler and registers @@ -132,7 +133,7 @@ func OAuth2TokenIntrospectionFactory(config *Config, storage interface{}, strate // If you need revocation, you can validate JWTs statefully, using the other factories. func OAuth2StatelessJWTIntrospectionFactory(config *Config, storage interface{}, strategy interface{}) interface{} { return &oauth2.StatelessJWTValidator{ - JWTAccessTokenStrategy: strategy.(oauth2.JWTAccessTokenStrategy), - ScopeStrategy: config.GetScopeStrategy(), + JWTStrategy: strategy.(jwt.JWTStrategy), + ScopeStrategy: config.GetScopeStrategy(), } } diff --git a/compose/config.go b/compose/config.go index e04dfc4c4..1466dfe09 100644 --- a/compose/config.go +++ b/compose/config.go @@ -95,6 +95,10 @@ type Config struct { // MinParameterEntropy controls the minimum size of state and nonce parameters. Defaults to fosite.MinParameterEntropy. MinParameterEntropy int + + // UseLegacyErrorFormat controls whether the legacy error format (with `error_debug`, `error_hint`, ...) + // should be used or not. + UseLegacyErrorFormat bool } // GetScopeStrategy returns the scope strategy to be used. Defaults to glob scope strategy. diff --git a/docs/image/banner_fosite.png b/docs/image/banner_fosite.png index 626da5692..924457d8d 100644 Binary files a/docs/image/banner_fosite.png and b/docs/image/banner_fosite.png differ diff --git a/errors.go b/errors.go index 96fbf2826..0fd944e21 100644 --- a/errors.go +++ b/errors.go @@ -26,6 +26,11 @@ import ( "fmt" "net/http" "net/url" + "strings" + + "github.com/ory/x/errorsx" + + stderr "errors" "github.com/pkg/errors" ) @@ -38,175 +43,180 @@ var ( // consistency of Update & Delete operations on the same rows between multiple sessions. ErrSerializationFailure = errors.New("The request could not be completed due to concurrent access") ErrUnknownRequest = &RFC6749Error{ - Name: errUnknownErrorName, - Description: "The handler is not responsible for this request", - Code: http.StatusBadRequest, + ErrorField: errUnknownErrorName, + DescriptionField: "The handler is not responsible for this request.", + CodeField: http.StatusBadRequest, } ErrRequestForbidden = &RFC6749Error{ - Name: errRequestForbidden, - Description: "The request is not allowed", - Hint: "You are not allowed to perform this action.", - Code: http.StatusForbidden, + ErrorField: errRequestForbidden, + DescriptionField: "The request is not allowed.", + HintField: "You are not allowed to perform this action.", + CodeField: http.StatusForbidden, } ErrInvalidRequest = &RFC6749Error{ - Name: errInvalidRequestName, - Description: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed", - Hint: "Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.", - Code: http.StatusBadRequest, + ErrorField: errInvalidRequestName, + DescriptionField: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.", + HintField: "Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.", + CodeField: http.StatusBadRequest, } ErrUnauthorizedClient = &RFC6749Error{ - Name: errUnauthorizedClientName, - Description: "The client is not authorized to request a token using this method", - Hint: "Make sure that client id and secret are correctly specified and that the client exists.", - Code: http.StatusBadRequest, + ErrorField: errUnauthorizedClientName, + DescriptionField: "The client is not authorized to request a token using this method.", + HintField: "Make sure that client id and secret are correctly specified and that the client exists.", + CodeField: http.StatusBadRequest, } ErrAccessDenied = &RFC6749Error{ - Name: errAccessDeniedName, - Description: "The resource owner or authorization server denied the request", - Hint: "Make sure that the request you are making is valid. Maybe the credential or request parameters you are using are limited in scope or otherwise restricted.", - Code: http.StatusForbidden, + ErrorField: errAccessDeniedName, + DescriptionField: "The resource owner or authorization server denied the request.", + HintField: "Make sure that the request you are making is valid. Maybe the credential or request parameters you are using are limited in scope or otherwise restricted.", + CodeField: http.StatusForbidden, } ErrUnsupportedResponseType = &RFC6749Error{ - Name: errUnsupportedResponseTypeName, - Description: "The authorization server does not support obtaining a token using this method", - Code: http.StatusBadRequest, + ErrorField: errUnsupportedResponseTypeName, + DescriptionField: "The authorization server does not support obtaining a token using this method.", + CodeField: http.StatusBadRequest, + } + ErrUnsupportedResponseMode = &RFC6749Error{ + ErrorField: errUnsupportedResponseModeName, + DescriptionField: "The authorization server does not support obtaining a response using this response mode.", + CodeField: http.StatusBadRequest, } ErrInvalidScope = &RFC6749Error{ - Name: errInvalidScopeName, - Description: "The requested scope is invalid, unknown, or malformed", - Code: http.StatusBadRequest, + ErrorField: errInvalidScopeName, + DescriptionField: "The requested scope is invalid, unknown, or malformed.", + CodeField: http.StatusBadRequest, } ErrServerError = &RFC6749Error{ - Name: errServerErrorName, - Description: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request", - Code: http.StatusInternalServerError, + ErrorField: errServerErrorName, + DescriptionField: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.", + CodeField: http.StatusInternalServerError, } ErrTemporarilyUnavailable = &RFC6749Error{ - Name: errTemporarilyUnavailableName, - Description: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server", - Code: http.StatusServiceUnavailable, + ErrorField: errTemporarilyUnavailableName, + DescriptionField: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.", + CodeField: http.StatusServiceUnavailable, } ErrUnsupportedGrantType = &RFC6749Error{ - Name: errUnsupportedGrantTypeName, - Description: "The authorization grant type is not supported by the authorization server", - Code: http.StatusBadRequest, + ErrorField: errUnsupportedGrantTypeName, + DescriptionField: "The authorization grant type is not supported by the authorization server.", + CodeField: http.StatusBadRequest, } ErrInvalidGrant = &RFC6749Error{ - Name: errInvalidGrantName, - Description: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client", - Code: http.StatusBadRequest, + ErrorField: errInvalidGrantName, + DescriptionField: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.", + CodeField: http.StatusBadRequest, } ErrInvalidClient = &RFC6749Error{ - Name: errInvalidClientName, - Description: "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)", - Code: http.StatusUnauthorized, + ErrorField: errInvalidClientName, + DescriptionField: "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).", + CodeField: http.StatusUnauthorized, } ErrInvalidState = &RFC6749Error{ - Name: errInvalidStateName, - Description: "The state is missing or does not have enough characters and is therefore considered too weak", - Code: http.StatusBadRequest, + ErrorField: errInvalidStateName, + DescriptionField: "The state is missing or does not have enough characters and is therefore considered too weak.", + CodeField: http.StatusBadRequest, } ErrMisconfiguration = &RFC6749Error{ - Name: errMisconfigurationName, - Description: "The request failed because of an internal error that is probably caused by misconfiguration", - Code: http.StatusInternalServerError, + ErrorField: errMisconfigurationName, + DescriptionField: "The request failed because of an internal error that is probably caused by misconfiguration.", + CodeField: http.StatusInternalServerError, } ErrInsufficientEntropy = &RFC6749Error{ - Name: errInsufficientEntropyName, - Description: "The request used a security parameter (e.g., anti-replay, anti-csrf) with insufficient entropy", - Code: http.StatusBadRequest, + ErrorField: errInsufficientEntropyName, + DescriptionField: "The request used a security parameter (e.g., anti-replay, anti-csrf) with insufficient entropy.", + CodeField: http.StatusBadRequest, } ErrNotFound = &RFC6749Error{ - Name: errNotFoundName, - Description: "Could not find the requested resource(s)", - Code: http.StatusNotFound, + ErrorField: errNotFoundName, + DescriptionField: "Could not find the requested resource(s).", + CodeField: http.StatusNotFound, } ErrRequestUnauthorized = &RFC6749Error{ - Name: errRequestUnauthorizedName, - Description: "The request could not be authorized", - Hint: "Check that you provided valid credentials in the right format.", - Code: http.StatusUnauthorized, + ErrorField: errRequestUnauthorizedName, + DescriptionField: "The request could not be authorized.", + HintField: "Check that you provided valid credentials in the right format.", + CodeField: http.StatusUnauthorized, } ErrTokenSignatureMismatch = &RFC6749Error{ - Name: errTokenSignatureMismatchName, - Description: "Token signature mismatch", - Hint: "Check that you provided a valid token in the right format.", - Code: http.StatusBadRequest, + ErrorField: errTokenSignatureMismatchName, + DescriptionField: "Token signature mismatch.", + HintField: "Check that you provided a valid token in the right format.", + CodeField: http.StatusBadRequest, } ErrInvalidTokenFormat = &RFC6749Error{ - Name: errInvalidTokenFormatName, - Description: "Invalid token format", - Hint: "Check that you provided a valid token in the right format.", - Code: http.StatusBadRequest, + ErrorField: errInvalidTokenFormatName, + DescriptionField: "Invalid token format.", + HintField: "Check that you provided a valid token in the right format.", + CodeField: http.StatusBadRequest, } ErrTokenExpired = &RFC6749Error{ - Name: errTokenExpiredName, - Description: "Token expired", - Hint: "The token expired.", - Code: http.StatusUnauthorized, + ErrorField: errTokenExpiredName, + DescriptionField: "Token expired.", + HintField: "The token expired.", + CodeField: http.StatusUnauthorized, } ErrScopeNotGranted = &RFC6749Error{ - Name: errScopeNotGrantedName, - Description: "The token was not granted the requested scope", - Hint: "The resource owner did not grant the requested scope.", - Code: http.StatusForbidden, + ErrorField: errScopeNotGrantedName, + DescriptionField: "The token was not granted the requested scope.", + HintField: "The resource owner did not grant the requested scope.", + CodeField: http.StatusForbidden, } ErrTokenClaim = &RFC6749Error{ - Name: errTokenClaimName, - Description: "The token failed validation due to a claim mismatch", - Hint: "One or more token claims failed validation.", - Code: http.StatusUnauthorized, + ErrorField: errTokenClaimName, + DescriptionField: "The token failed validation due to a claim mismatch.", + HintField: "One or more token claims failed validation.", + CodeField: http.StatusUnauthorized, } ErrInactiveToken = &RFC6749Error{ - Name: errTokenInactiveName, - Description: "Token is inactive because it is malformed, expired or otherwise invalid", - Hint: "Token validation failed.", - Code: http.StatusUnauthorized, + ErrorField: errTokenInactiveName, + DescriptionField: "Token is inactive because it is malformed, expired or otherwise invalid.", + HintField: "Token validation failed.", + CodeField: http.StatusUnauthorized, } ErrLoginRequired = &RFC6749Error{ - Name: errLoginRequired, - Description: "The Authorization Server requires End-User authentication", - Code: http.StatusBadRequest, + ErrorField: errLoginRequired, + DescriptionField: "The Authorization Server requires End-User authentication.", + CodeField: http.StatusBadRequest, } ErrInteractionRequired = &RFC6749Error{ - Description: "The Authorization Server requires End-User interaction of some form to proceed", - Name: errInteractionRequired, - Code: http.StatusBadRequest, + DescriptionField: "The Authorization Server requires End-User interaction of some form to proceed.", + ErrorField: errInteractionRequired, + CodeField: http.StatusBadRequest, } ErrConsentRequired = &RFC6749Error{ - Description: "The Authorization Server requires End-User consent", - Name: errConsentRequired, - Code: http.StatusBadRequest, + DescriptionField: "The Authorization Server requires End-User consent.", + ErrorField: errConsentRequired, + CodeField: http.StatusBadRequest, } ErrRequestNotSupported = &RFC6749Error{ - Description: "The OP does not support use of the request parameter", - Name: errRequestNotSupportedName, - Code: http.StatusBadRequest, + DescriptionField: "The OP does not support use of the request parameter.", + ErrorField: errRequestNotSupportedName, + CodeField: http.StatusBadRequest, } ErrRequestURINotSupported = &RFC6749Error{ - Description: "The OP does not support use of the request_uri parameter", - Name: errRequestURINotSupportedName, - Code: http.StatusBadRequest, + DescriptionField: "The OP does not support use of the request_uri parameter.", + ErrorField: errRequestURINotSupportedName, + CodeField: http.StatusBadRequest, } ErrRegistrationNotSupported = &RFC6749Error{ - Description: "The OP does not support use of the registration parameter", - Name: errRegistrationNotSupportedName, - Code: http.StatusBadRequest, + DescriptionField: "The OP does not support use of the registration parameter.", + ErrorField: errRegistrationNotSupportedName, + CodeField: http.StatusBadRequest, } ErrInvalidRequestURI = &RFC6749Error{ - Description: "The request_uri in the Authorization Request returns an error or contains invalid data. ", - Name: errInvalidRequestURI, - Code: http.StatusBadRequest, + DescriptionField: "The request_uri in the Authorization Request returns an error or contains invalid data.", + ErrorField: errInvalidRequestURI, + CodeField: http.StatusBadRequest, } ErrInvalidRequestObject = &RFC6749Error{ - Description: "The request parameter contains an invalid Request Object. ", - Name: errInvalidRequestObject, - Code: http.StatusBadRequest, + DescriptionField: "The request parameter contains an invalid Request Object.", + ErrorField: errInvalidRequestObject, + CodeField: http.StatusBadRequest, } ErrJTIKnown = &RFC6749Error{ - Description: "The jti was already used.", - Name: errJTIKnownName, - Code: http.StatusBadRequest, + DescriptionField: "The jti was already used.", + ErrorField: errJTIKnownName, + CodeField: http.StatusBadRequest, } ) @@ -222,6 +232,7 @@ const ( errUnauthorizedClientName = "unauthorized_client" errAccessDeniedName = "access_denied" errUnsupportedResponseTypeName = "unsupported_response_type" + errUnsupportedResponseModeName = "unsupported_response_mode" errInvalidScopeName = "invalid_scope" errServerErrorName = "server_error" errTemporarilyUnavailableName = "temporarily_unavailable" @@ -246,35 +257,103 @@ const ( errJTIKnownName = "jti_known" ) +type ( + RFC6749Error struct { + ErrorField string + DescriptionField string + HintField string + CodeField int + DebugField string + cause error + useLegacyFormat bool + exposeDebug bool + } + stackTracer interface { + StackTrace() errors.StackTrace + } +) + +var ( + _ errorsx.DebugCarrier = new(RFC6749Error) + _ errorsx.ReasonCarrier = new(RFC6749Error) + _ errorsx.RequestIDCarrier = new(RFC6749Error) + _ errorsx.StatusCarrier = new(RFC6749Error) + _ errorsx.StatusCodeCarrier = new(RFC6749Error) + // _ errorsx.DetailsCarrier = new(RFC6749Error) +) + func ErrorToRFC6749Error(err error) *RFC6749Error { var e *RFC6749Error if errors.As(err, &e) { return e } return &RFC6749Error{ - Name: errUnknownErrorName, - Description: "The error is unrecognizable.", - Debug: err.Error(), - Code: http.StatusInternalServerError, - cause: err, + ErrorField: errUnknownErrorName, + DescriptionField: "The error is unrecognizable", + DebugField: err.Error(), + CodeField: http.StatusInternalServerError, + cause: err, + } +} + +// StackTrace returns the error's stack trace. +func (e *RFC6749Error) StackTrace() (trace errors.StackTrace) { + if e.cause == e || e.cause == nil { + return + } + + if st := stackTracer(nil); stderr.As(e.cause, &st) { + trace = st.StackTrace() + } + + return +} + +func (e RFC6749Error) Unwrap() error { + return e.cause +} + +func (e *RFC6749Error) Wrap(err error) { + e.cause = err +} + +func (e RFC6749Error) WithWrap(cause error) *RFC6749Error { + e.cause = cause + return &e +} + +func (e RFC6749Error) WithLegacyFormat(useLegacyFormat bool) *RFC6749Error { + e.useLegacyFormat = useLegacyFormat + return &e +} + +func (e *RFC6749Error) WithTrace(err error) *RFC6749Error { + if st := stackTracer(nil); !stderr.As(e.cause, &st) { + e.Wrap(errorsx.WithStack(err)) + } else { + e.Wrap(err) } + return e } -type RFC6749Error struct { - Name string - Description string - Hint string - Code int - Debug string - cause error +func (e RFC6749Error) Is(err error) bool { + switch te := err.(type) { + case RFC6749Error: + return e.ErrorField == te.ErrorField && + e.CodeField == te.CodeField + case *RFC6749Error: + return e.ErrorField == te.ErrorField && + e.CodeField == te.CodeField + } + return false } func (e *RFC6749Error) Status() string { - return http.StatusText(e.Code) + return http.StatusText(e.CodeField) } -func (e *RFC6749Error) Error() string { - return e.Name +func (e RFC6749Error) Error() string { + return e.ErrorField } func (e *RFC6749Error) RequestID() string { @@ -282,34 +361,34 @@ func (e *RFC6749Error) RequestID() string { } func (e *RFC6749Error) Reason() string { - return e.Hint + return e.HintField } func (e *RFC6749Error) StatusCode() int { - return e.Code + return e.CodeField } func (e *RFC6749Error) Cause() error { return e.cause } -func (e *RFC6749Error) Unwrap() error { - return e.cause -} - func (e *RFC6749Error) WithHintf(hint string, args ...interface{}) *RFC6749Error { return e.WithHint(fmt.Sprintf(hint, args...)) } func (e *RFC6749Error) WithHint(hint string) *RFC6749Error { err := *e - err.Hint = hint + err.HintField = hint return &err } +func (e *RFC6749Error) Debug() string { + return e.DebugField +} + func (e *RFC6749Error) WithDebug(debug string) *RFC6749Error { err := *e - err.Debug = debug + err.DebugField = debug return &err } @@ -319,43 +398,41 @@ func (e *RFC6749Error) WithDebugf(debug string, args ...interface{}) *RFC6749Err func (e *RFC6749Error) WithDescription(description string) *RFC6749Error { err := *e - err.Description = description + err.DescriptionField = description return &err } -func (e *RFC6749Error) WithCause(cause error) *RFC6749Error { +// Sanitize strips the debug field +// +// Deprecated: Use WithExposeDebug instead. +func (e *RFC6749Error) Sanitize() *RFC6749Error { err := *e - err.cause = cause + err.DebugField = "" return &err } -func (e *RFC6749Error) Sanitize() *RFC6749Error { +// WithExposeDebug if set to true exposes debug messages +func (e *RFC6749Error) WithExposeDebug(exposeDebug bool) *RFC6749Error { err := *e - err.Debug = "" + err.exposeDebug = exposeDebug return &err } // GetDescription returns a more description description, combined with hint and debug (when available). func (e *RFC6749Error) GetDescription() string { - description := e.Description - if e.Hint != "" { - description += "\n\n" + e.Hint + description := e.DescriptionField + if e.HintField != "" { + description += " " + e.HintField } - if e.Debug != "" { - description += "\n\n" + e.Debug + if e.DebugField != "" && e.exposeDebug { + description += " " + e.DebugField } - return description -} - -// Is returns true if the target error is equal to the current error. Used by errors.Is. -func (e *RFC6749Error) Is(target error) bool { - return e.Error() == target.Error() + return strings.ReplaceAll(description, "\"", "'") } // RFC6749ErrorJson is a helper struct for JSON encoding/decoding of RFC6749Error. type RFC6749ErrorJson struct { Name string `json:"error"` - Verbose string `json:"error_verbose"` Description string `json:"error_description"` Hint string `json:"error_hint,omitempty"` Code int `json:"status_code,omitempty"` @@ -369,36 +446,56 @@ func (e *RFC6749Error) UnmarshalJSON(b []byte) error { return err } - e.Name = data.Name - e.Description = data.Verbose - e.Hint = data.Hint - e.Code = data.Code - e.Debug = data.Debug + e.ErrorField = data.Name + e.CodeField = data.Code + e.DescriptionField = data.Description + + if len(data.Hint+data.Debug) > 0 { + e.HintField = data.Hint + e.DebugField = data.Debug + e.useLegacyFormat = true + } return nil } func (e RFC6749Error) MarshalJSON() ([]byte, error) { - data := RFC6749ErrorJson{ - Name: e.Name, - Verbose: e.Description, - Description: e.GetDescription(), - Hint: e.Hint, - Code: e.Code, - Debug: e.Debug, - } - return json.Marshal(data) + if !e.useLegacyFormat { + return json.Marshal(&RFC6749ErrorJson{ + Name: e.ErrorField, + Description: e.GetDescription(), + }) + } + + var debug string + if e.exposeDebug { + debug = e.DebugField + } + + return json.Marshal(&RFC6749ErrorJson{ + Name: e.ErrorField, + Description: e.DescriptionField, + Hint: e.HintField, + Code: e.CodeField, + Debug: debug, + }) } func (e *RFC6749Error) ToValues() url.Values { values := url.Values{} - values.Add("error", e.Name) - values.Add("error_description", e.GetDescription()) - if e.Hint != "" { - values.Add("error_hint", e.Hint) - } - if e.Debug != "" { - values.Add("error_debug", e.Debug) + values.Set("error", e.ErrorField) + values.Set("error_description", e.GetDescription()) + + if e.useLegacyFormat { + values.Set("error_description", e.DescriptionField) + if e.HintField != "" { + values.Set("error_hint", e.HintField) + } + + if e.DebugField != "" && e.exposeDebug { + values.Set("error_debug", e.DebugField) + } } + return values } diff --git a/errors_test.go b/errors_test.go index 424f722ed..9f0a26252 100644 --- a/errors_test.go +++ b/errors_test.go @@ -28,12 +28,19 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIs(t *testing.T) { - assert.True(t, errors.Is(ErrUnknownRequest, ErrUnknownRequest)) - assert.True(t, errors.Is(ErrUnknownRequest, &RFC6749Error{ - Name: errUnknownErrorName, - })) - assert.True(t, errors.Is(&RFC6749Error{ - Name: errUnknownErrorName, - }, ErrUnknownRequest)) +func TestRFC6749Error(t *testing.T) { + t.Run("case=wrap", func(t *testing.T) { + orig := errors.New("hi") + wrap := new(RFC6749Error) + wrap.Wrap(orig) + + assert.EqualValues(t, orig.(stackTracer).StackTrace(), wrap.StackTrace()) + }) + + t.Run("case=wrap_self", func(t *testing.T) { + wrap := new(RFC6749Error) + wrap.Wrap(wrap) + + assert.Empty(t, wrap.StackTrace()) + }) } diff --git a/fosite.go b/fosite.go index e3120a18c..11e470601 100644 --- a/fosite.go +++ b/fosite.go @@ -22,6 +22,7 @@ package fosite import ( + "html/template" "net/http" "reflect" ) @@ -94,6 +95,7 @@ type Fosite struct { AudienceMatchingStrategy AudienceMatchingStrategy JWKSFetcherStrategy JWKSFetcherStrategy HTTPClient *http.Client + UseLegacyErrorFormat bool // TokenURL is the the URL of the Authorization Server's Token Endpoint. TokenURL string @@ -105,6 +107,9 @@ type Fosite struct { // MinParameterEntropy controls the minimum size of state and nonce parameters. Defaults to fosite.MinParameterEntropy. MinParameterEntropy int + + // FormPostHTMLTemplate sets html template for rendering the authorization response when the request has response_mode=form_post. Defaults to fosite.FormPostDefaultTemplate + FormPostHTMLTemplate *template.Template } const MinParameterEntropy = 8 diff --git a/go.mod b/go.mod index 08aebe542..55798c542 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/dgraph-io/ristretto v0.0.3 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 // indirect github.com/golang/mock v1.4.3 github.com/golang/protobuf v1.4.0 // indirect github.com/gorilla/mux v1.7.3 @@ -12,21 +11,21 @@ require ( github.com/magiconair/properties v1.8.1 github.com/mattn/goveralls v0.0.6 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b // indirect github.com/oleiade/reflections v1.0.0 github.com/ory/go-acc v0.2.5 github.com/ory/go-convenience v0.1.0 + github.com/ory/x v0.0.162 github.com/parnurzeal/gorequest v0.2.15 github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.3.2 // indirect - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 + golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 // indirect golang.org/x/text v0.3.3 // indirect golang.org/x/tools v0.0.0-20200721223218-6123e77877b2 // indirect - google.golang.org/appengine v1.6.5 // indirect gopkg.in/square/go-jose.v2 v2.5.1 ) diff --git a/go.sum b/go.sum index 42d845186..b4c84b1f8 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,71 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20200312223839-f565e4789405/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -33,65 +79,408 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 h1:LCoguo7Zd0MByKMbQbTvcZw7HiBcbvew+MOcwsJVwrY= github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.1.0/go.mod h1:fmNpaWyHM0tRm8gCZWKx8yY9fvaNLo2PyzBNSrBZ5Hw= +github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= +github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= +github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= +github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= +github.com/gobuffalo/buffalo-plugins v1.4.3/go.mod h1:uCzTY0woez4nDMdQjkcOYKanngeUVRO2HZi7ezmAjWY= +github.com/gobuffalo/buffalo-plugins v1.5.1/go.mod h1:jbmwSZK5+PiAP9cC09VQOrGMZFCa/P0UMlIS3O12r5w= +github.com/gobuffalo/buffalo-plugins v1.6.4/go.mod h1:/+N1aophkA2jZ1ifB2O3Y9yGwu6gKOVMtUmJnbg+OZI= +github.com/gobuffalo/buffalo-plugins v1.6.5/go.mod h1:0HVkbgrVs/MnPZ/FOseDMVanCTm2RNcdM0PuXcL1NNI= +github.com/gobuffalo/buffalo-plugins v1.6.7/go.mod h1:ZGZRkzz2PiKWHs0z7QsPBOTo2EpcGRArMEym6ghKYgk= +github.com/gobuffalo/buffalo-plugins v1.6.9/go.mod h1:yYlYTrPdMCz+6/+UaXg5Jm4gN3xhsvsQ2ygVatZV5vw= +github.com/gobuffalo/buffalo-plugins v1.6.11/go.mod h1:eAA6xJIL8OuynJZ8amXjRmHND6YiusVAaJdHDN1Lu8Q= +github.com/gobuffalo/buffalo-plugins v1.8.2/go.mod h1:9te6/VjEQ7pKp7lXlDIMqzxgGpjlKoAcAANdCgoR960= +github.com/gobuffalo/buffalo-plugins v1.8.3/go.mod h1:IAWq6vjZJVXebIq2qGTLOdlXzmpyTZ5iJG5b59fza5U= +github.com/gobuffalo/buffalo-plugins v1.9.4/go.mod h1:grCV6DGsQlVzQwk6XdgcL3ZPgLm9BVxlBmXPMF8oBHI= +github.com/gobuffalo/buffalo-plugins v1.10.0/go.mod h1:4osg8d9s60txLuGwXnqH+RCjPHj9K466cDFRl3PErHI= +github.com/gobuffalo/buffalo-plugins v1.11.0/go.mod h1:rtIvAYRjYibgmWhnjKmo7OadtnxuMG5ZQLr25ozAzjg= +github.com/gobuffalo/buffalo-plugins v1.15.0/go.mod h1:BqSx01nwgKUQr/MArXzFkSD0QvdJidiky1OKgyfgrK8= +github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= +github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.6/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.7/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.8/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.9/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.10/go.mod h1:X0CFllQjTV5ogsnUrg+Oks2yTI+PU2dGYBJOEI2D1Uo= +github.com/gobuffalo/envy v1.6.11/go.mod h1:Fiq52W7nrHGDggFPhn2ZCcHw4u/rqXkqo+i7FB6EAcg= +github.com/gobuffalo/envy v1.6.12/go.mod h1:qJNrJhKkZpEW0glh5xP2syQHH5kgdmgsKss2Kk8PTP0= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= +github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= +github.com/gobuffalo/events v1.0.8/go.mod h1:A5KyqT1sA+3GJiBE4QKZibse9mtOcI9nw8gGrDdqYGs= +github.com/gobuffalo/events v1.1.3/go.mod h1:9yPGWYv11GENtzrIRApwQRMYSbUgCsZ1w6R503fCfrk= +github.com/gobuffalo/events v1.1.4/go.mod h1:09/YRRgZHEOts5Isov+g9X2xajxdvOAcUuAHIX/O//A= +github.com/gobuffalo/events v1.1.5/go.mod h1:3YUSzgHfYctSjEjLCWbkXP6djH2M+MLaVRzb4ymbAK0= +github.com/gobuffalo/events v1.1.7/go.mod h1:6fGqxH2ing5XMb3EYRq9LEkVlyPGs4oO/eLzh+S8CxY= +github.com/gobuffalo/events v1.1.8/go.mod h1:UFy+W6X6VbCWS8k2iT81HYX65dMtiuVycMy04cplt/8= +github.com/gobuffalo/events v1.1.9/go.mod h1:/0nf8lMtP5TkgNbzYxR6Bl4GzBy5s5TebgNTdRfRbPM= +github.com/gobuffalo/events v1.3.1/go.mod h1:9JOkQVoyRtailYVE/JJ2ZQ/6i4gTjM5t2HsZK4C1cSA= +github.com/gobuffalo/events v1.4.1/go.mod h1:SjXgWKpeSuvQDvGhgMz5IXx3Czu+IbL+XPLR41NvVQY= +github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= +github.com/gobuffalo/fizz v1.9.8/go.mod h1:w1FEn1yKNVCc49KnADGyYGRPH7jFON3ak4Bj1yUudHo= +github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181018182602-fd24a256709f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181019110701-3d6f0b585514/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181024204909-8f6be1a8c6c2/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181104133451-1f6e9779237a/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181114183036-47375f6d8328/go.mod h1:0HvNbHdfh+WOvDSIASqJOSxTOWSxCCUF++k/Y53v9rI= +github.com/gobuffalo/flect v0.0.0-20181210151238-24a2b68e0316/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190104192022-4af577e09bf2/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190117212819-a62e61d96794/go.mod h1:397QT6v05LkZkn07oJXXT6y9FCfwC8Pug0WA2/2mE9k= +github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= +github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181017160347-90a774534246/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181024195656-51392254bf53/go.mod h1:o9GEH5gn5sCKLVB5rHFC4tq40rQ3VRUzmx6WwmaqISE= +github.com/gobuffalo/genny v0.0.0-20181025145300-af3f81d526b8/go.mod h1:uZ1fFYvdcP8mu0B/Ynarf6dsGvp7QFIpk/QACUuFUVI= +github.com/gobuffalo/genny v0.0.0-20181027191429-94d6cfb5c7fc/go.mod h1:x7SkrQQBx204Y+O9EwRXeszLJDTaWN0GnEasxgLrQTA= +github.com/gobuffalo/genny v0.0.0-20181027195209-3887b7171c4f/go.mod h1:JbKx8HSWICu5zyqWOa0dVV1pbbXOHusrSzQUprW6g+w= +github.com/gobuffalo/genny v0.0.0-20181106193839-7dcb0924caf1/go.mod h1:x61yHxvbDCgQ/7cOAbJCacZQuHgB0KMSzoYcw5debjU= +github.com/gobuffalo/genny v0.0.0-20181107223128-f18346459dbe/go.mod h1:utQD3aKKEsdb03oR+Vi/6ztQb1j7pO10N3OBoowRcSU= +github.com/gobuffalo/genny v0.0.0-20181114215459-0a4decd77f5d/go.mod h1:kN2KZ8VgXF9VIIOj/GM0Eo7YK+un4Q3tTreKOf0q1ng= +github.com/gobuffalo/genny v0.0.0-20181119162812-e8ff4adce8bb/go.mod h1:BA9htSe4bZwBDJLe8CUkoqkypq3hn3+CkoHqVOW718E= +github.com/gobuffalo/genny v0.0.0-20181127225641-2d959acc795b/go.mod h1:l54xLXNkteX/PdZ+HlgPk1qtcrgeOr3XUBBPDbH+7CQ= +github.com/gobuffalo/genny v0.0.0-20181128191930-77e34f71ba2a/go.mod h1:FW/D9p7cEEOqxYA71/hnrkOWm62JZ5ZNxcNIVJEaWBU= +github.com/gobuffalo/genny v0.0.0-20181203165245-fda8bcce96b1/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181203201232-849d2c9534ea/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181206121324-d6fb8a0dbe36/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181207164119-84844398a37d/go.mod h1:y0ysCHGGQf2T3vOhCrGHheYN54Y/REj0ayd0Suf4C/8= +github.com/gobuffalo/genny v0.0.0-20181211165820-e26c8466f14d/go.mod h1:sHnK+ZSU4e2feXP3PA29ouij6PUEiN+RCwECjCTB3yM= +github.com/gobuffalo/genny v0.0.0-20190104222617-a71664fc38e7/go.mod h1:QPsQ1FnhEsiU8f+O0qKWXz2RE4TiDqLVChWkBuh1WaY= +github.com/gobuffalo/genny v0.0.0-20190112155932-f31a84fcacf5/go.mod h1:CIaHCrSIuJ4il6ka3Hub4DR4adDrGoXGEEt2FbBxoIo= +github.com/gobuffalo/genny v0.2.0/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.3.0/go.mod h1:ywJ2CoXrTZj7rbS8HTbzv7uybnLKlsNSBhEQ+yFI3E8= +github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA= +github.com/gobuffalo/genny/v2 v2.0.5/go.mod h1:kRkJuAw9mdI37AiEYjV4Dl+TgkBDYf8HZVjLkqe5eBg= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= +github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= +github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI= +github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic= +github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/helpers v0.2.2/go.mod h1:xYbzUdCUpVzLwLnqV8HIjT6hmG0Cs7YIBCJkNM597jw= +github.com/gobuffalo/helpers v0.2.4/go.mod h1:NX7v27yxPDOPTgUFYmJ5ow37EbxdoLraucOGvMNawyk= +github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4= +github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL6dkUOhyv8= +github.com/gobuffalo/helpers v0.6.1/go.mod h1:wInbDi0vTJKZBviURTLRMFLE4+nF2uRuuL2fnlYo7w4= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= +github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= +github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw= +github.com/gobuffalo/licenser v0.0.0-20181027200154-58051a75da95/go.mod h1:BzhaaxGd1tq1+OLKObzgdCV9kqVhbTulxOpYbvMQWS0= +github.com/gobuffalo/licenser v0.0.0-20181109171355-91a2a7aac9a7/go.mod h1:m+Ygox92pi9bdg+gVaycvqE8RVSjZp7mWw75+K5NPHk= +github.com/gobuffalo/licenser v0.0.0-20181128165715-cc7305f8abed/go.mod h1:oU9F9UCE+AzI/MueCKZamsezGOOHfSirltllOVeRTAE= +github.com/gobuffalo/licenser v0.0.0-20181203160806-fe900bbede07/go.mod h1:ph6VDNvOzt1CdfaWC+9XwcBnlSTBz2j49PBwum6RFaU= +github.com/gobuffalo/licenser v0.0.0-20181211173111-f8a311c51159/go.mod h1:ve/Ue99DRuvnTaLq2zKa6F4KtHiYf7W046tDjuGYPfM= +github.com/gobuffalo/licenser v1.1.0/go.mod h1:ZVWE6uKUE3rGf7sedUHWVjNWrEgxaUQLVFL+pQiWpfY= +github.com/gobuffalo/logger v0.0.0-20181022175615-46cfb361fc27/go.mod h1:8sQkgyhWipz1mIctHF4jTxmJh1Vxhp7mP8IqbljgJZo= +github.com/gobuffalo/logger v0.0.0-20181027144941-73d08d2bb969/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181027193913-9cf4dd0efe46/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181109185836-3feeab578c17/go.mod h1:oNErH0xLe+utO+OW8ptXMSA5DkiSEDW1u3zGIt8F9Ew= +github.com/gobuffalo/logger v0.0.0-20181117211126-8e9b89b7c264/go.mod h1:5etB91IE0uBlw9k756fVKZJdS+7M7ejVhmpXXiSFj0I= +github.com/gobuffalo/logger v0.0.0-20181127160119-5b956e21995c/go.mod h1:+HxKANrR9VGw9yN3aOAppJKvhO05ctDi63w4mDnKv2U= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= +github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.1.0/go.mod h1:pqQ1XAqvpy/JYtRwoieNps2yU8MFiMxBUpAm2FBtQ50= +github.com/gobuffalo/mapi v1.2.1/go.mod h1:giGJ2AUESRepOFYAzWpq8Gf/s/QDryxoEHisQtFN3cY= +github.com/gobuffalo/meta v0.0.0-20181018155829-df62557efcd3/go.mod h1:XTTOhwMNryif3x9LkTTBO/Llrveezd71u3quLd0u7CM= +github.com/gobuffalo/meta v0.0.0-20181018192820-8c6cef77dab3/go.mod h1:E94EPzx9NERGCY69UWlcj6Hipf2uK/vnfrF4QD0plVE= +github.com/gobuffalo/meta v0.0.0-20181025145500-3a985a084b0a/go.mod h1:YDAKBud2FP7NZdruCSlmTmDOZbVSa6bpK7LJ/A/nlKg= +github.com/gobuffalo/meta v0.0.0-20181114191255-b130ebedd2f7/go.mod h1:K6cRZ29ozr4Btvsqkjvg5nDFTLOgTqf03KA70Ks0ypE= +github.com/gobuffalo/meta v0.0.0-20181127070345-0d7e59dd540b/go.mod h1:RLO7tMvE0IAKAM8wny1aN12pvEKn7EtkBLkUZR00Qf8= +github.com/gobuffalo/meta v0.0.0-20190120163247-50bbb1fa260d/go.mod h1:KKsH44nIK2gA8p0PJmRT9GvWJUdphkDUA8AJEvFWiqM= +github.com/gobuffalo/meta v0.0.0-20190329152330-e161e8a93e3b/go.mod h1:mCRSy5F47tjK8yaIDcJad4oe9fXxY5gLrx3Xx2spK+0= +github.com/gobuffalo/meta v0.3.0/go.mod h1:cpr6mrUX5H/B4wEP86Gdq568TK4+dKUD8oRPl698RUw= +github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= +github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= +github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= +github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= +github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= +github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= +github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= +github.com/gobuffalo/nulls v0.2.0/go.mod h1:w4q8RoSCEt87Q0K0sRIZWYeIxkxog5mh3eN3C/n+dUc= +github.com/gobuffalo/nulls v0.3.0/go.mod h1:UP49vd/k+bcaz6m0cHMyuk8oQ7XgLnkfxeiVoPAvBSs= +github.com/gobuffalo/packd v0.0.0-20181027182251-01ad393492c8/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027190505-aafc0d02c411/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027194105-7ae579e6d213/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181104210303-d376b15f8e96/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181111195323-b2e760a5f0ff/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181114190715-f25c5d2471d7/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181124090624-311c6248e5fb/go.mod h1:Foenia9ZvITEvG05ab6XpiD5EfBHPL8A6hush8SJ0o8= +github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.0.0-20181212173646-eca3b8fd6687/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.2.0/go.mod h1:k2CkHP3bjbqL2GwxwhxUy1DgnlbW644hkLC9iIUvZwY= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= +github.com/gobuffalo/packr v1.15.0/go.mod h1:t5gXzEhIviQwVlNx/+3SfS07GS+cZ2hn76WLzPp6MGI= +github.com/gobuffalo/packr v1.15.1/go.mod h1:IeqicJ7jm8182yrVmNbM6PR4g79SjN9tZLH8KduZZwE= +github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU= +github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecUga6884zw= +github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0= +github.com/gobuffalo/packr v1.22.0/go.mod h1:Qr3Wtxr3+HuQEwWqlLnNW4t1oTvK+7Gc/Rnoi/lDFvA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes= +github.com/gobuffalo/packr/v2 v2.0.0-rc.9/go.mod h1:fQqADRfZpEsgkc7c/K7aMew3n4aF1Kji7+lIZeR98Fc= +github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w= +github.com/gobuffalo/packr/v2 v2.0.0-rc.11/go.mod h1:JoieH/3h3U4UmatmV93QmqyPUdf4wVM9HELaHEu+3fk= +github.com/gobuffalo/packr/v2 v2.0.0-rc.12/go.mod h1:FV1zZTsVFi1DSCboO36Xgs4pzCZBjB/tDV9Cz/lSaR8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.13/go.mod h1:2Mp7GhBFMdJlOK8vGfl7SYtfMP3+5roE39ejlfjw0rA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.14/go.mod h1:06otbrNvDKO1eNQ3b8hst+1010UooI2MFg+B2Ze4MV8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.15/go.mod h1:IMe7H2nJvcKXSF90y4X1rjYIRlNMJYCxEhssBXNZwWs= +github.com/gobuffalo/packr/v2 v2.4.0/go.mod h1:ra341gygw9/61nSjAbfwcwh8IrYL4WmR4IsPkPBhQiY= +github.com/gobuffalo/packr/v2 v2.5.2/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.22+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.23+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.30+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.31+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.32+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.2+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0= +github.com/gobuffalo/plushgen v0.0.0-20181128164830-d29dcb966cb2/go.mod h1:r9QwptTFnuvSaSRjpSp4S2/4e2D3tJhARYbvEBcKSb4= +github.com/gobuffalo/plushgen v0.0.0-20181203163832-9fc4964505c2/go.mod h1:opEdT33AA2HdrIwK1aibqnTJDVVKXC02Bar/GT1YRVs= +github.com/gobuffalo/plushgen v0.0.0-20181207152837-eedb135bd51b/go.mod h1:Lcw7HQbEVm09sAQrCLzIxuhFbB3nAgp4c55E+UlynR0= +github.com/gobuffalo/plushgen v0.0.0-20190104222512-177cd2b872b3/go.mod h1:tYxCozi8X62bpZyKXYHw1ncx2ZtT2nFvG42kuLwYjoc= +github.com/gobuffalo/plushgen v0.1.2/go.mod h1:3U71v6HWZpVER1nInTXeAwdoRNsRd4W8aeIa1Lyp+Bk= +github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.13.1+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop/v5 v5.0.11/go.mod h1:mZJHJbA3cy2V18abXYuVop2ldEJ8UZ2DK6qOekC5u5g= +github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.42/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.52/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.53/go.mod h1:FdF257nd8rqhNaqtDWFGhxdJ/Ig4J7VcS3KL7n/a+aA= +github.com/gobuffalo/release v1.0.54/go.mod h1:Pe5/RxRa/BE8whDpGfRqSI7D1a0evGK1T4JDm339tJc= +github.com/gobuffalo/release v1.0.61/go.mod h1:mfIO38ujUNVDlBziIYqXquYfBF+8FDHUjKZgYC1Hj24= +github.com/gobuffalo/release v1.0.72/go.mod h1:NP5NXgg/IX3M5XmHmWR99D687/3Dt9qZtTK/Lbwc1hU= +github.com/gobuffalo/release v1.1.1/go.mod h1:Sluak1Xd6kcp6snkluR1jeXAogdJZpFFRzTYRs/2uwg= +github.com/gobuffalo/release v1.1.3/go.mod h1:CuXc5/m+4zuq8idoDt1l4va0AXAn/OSs08uHOfMVr8E= +github.com/gobuffalo/release v1.1.6/go.mod h1:18naWa3kBsqO0cItXZNJuefCKOENpbbUIqRL1g+p6z0= +github.com/gobuffalo/release v1.7.0/go.mod h1:xH2NjAueVSY89XgC4qx24ojEQ4zQ9XCGVs5eXwJTkEs= +github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= +github.com/gobuffalo/shoulders v1.0.4/go.mod h1:LqMcHhKRuBPMAYElqOe3POHiZ1x7Ry0BE8ZZ84Bx+k4= +github.com/gobuffalo/syncx v0.0.0-20181120191700-98333ab04150/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.1.0/go.mod h1:Mg/s+5pv7IgxEp6sA+NFpqS4o2x+R9dQNwbwT0iuOGQ= +github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.14+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.15+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.0+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/tags/v3 v3.1.0/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate v2.0.4+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate/v3 v3.0.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.1.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.2.0/go.mod h1:PrhDOdDHxtN8KUgMvF3TDL0r1YZXV4sQnyFX/EmeETY= +github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= +github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid/v3 v3.1.2/go.mod h1:xPwMqoocQ1L5G6pXX5BcE7N5jlzn2o19oqAKxwZW/kI= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20180828051604-96d2a289f41e/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2 h1:xisWqjiKEff2B0KfFYGpCqc3M3zdTz+OHQHRc09FeYk= +github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.4.1/go.mod h1:6iSW+JznC0YT+SgBn7rNxoEBsBgSmnC5FwyCekOGUiE= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.9/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.5/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -99,47 +488,150 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/luna-duclos/instrumentedsql v0.0.0-20181127104832-b7d587d28109/go.mod h1:PWUIzhtavmOR965zfawVsHXbEuU1G29BPZ/CB3C7jXk= +github.com/luna-duclos/instrumentedsql v1.1.2/go.mod h1:4LGbEqDnopzNAiyxPPDXhLspyunZxgPTMJBKtC6U0BQ= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.1.3/go.mod h1:BF7ioVzAJYEtzQN/os4rt8H8Ti3h0T7EoN+7eyALktE= +github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= +github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= +github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= +github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= +github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= +github.com/markbates/inflect v1.0.3/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= +github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= +github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y= github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b h1:Pip12xNtMvEFUBF4f8/b5yRXj94LLrNdLWELfOr2KcY= github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.0 h1:0ir4pc6v8/PJ0yw5AEtMddfXpWBXg9cnG7SgSoJuCgY= github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/analytics-go/v4 v4.0.0/go.mod h1:FMx9cLRD9xN+XevPvZ5FDMfignpmcqPP6FUKnJ9/MmE= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.5.4/go.mod h1:J8ZUbNB2FOhm1cFZW9xBpDsODqsSWcyYgtJYVPcnF70= +github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= +github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= github.com/ory/go-acc v0.2.5 h1:31irXHzG2vnKQSE4weJm7AdfrnpaVjVCq3nD7viXCJE= github.com/ory/go-acc v0.2.5/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= +github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8/go.mod h1:wsH1C4nIeeQClDtD5AH7kF1uTS6zWyqfjVDTmB0Em7A= +github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9/go.mod h1:BNZpdJgB74KOLSsWFvzw6roXg1I6O51WO8roMmW+T7Y= +github.com/ory/herodot v0.6.2/go.mod h1:3BOneqcyBsVybCPAJoi92KN2BpJHcmDqAMcAAaJiJow= +github.com/ory/herodot v0.7.0/go.mod h1:YXKOfAXYdQojDP5sD8m0ajowq3+QXNdtxA+QiUXBwn0= +github.com/ory/herodot v0.8.3/go.mod h1:rvLjxOAlU5omtmgjCfazQX2N82EpMfl3BytBWc1jjsk= +github.com/ory/jsonschema/v3 v3.0.1/go.mod h1:jgLHekkFk0uiGdEWGleC+tOm6JSSP8cbf17PnBuGXlw= +github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28= +github.com/ory/viper v1.7.4/go.mod h1:T6sodNZKNGPpashUOk7EtXz2isovz8oCd57GNVkkNmE= github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/ory/x v0.0.84/go.mod h1:RXLPBG7B+hAViONVg0sHwK+U/ie1Y/NeXrq1JcARfoE= +github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g= +github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE= +github.com/ory/x v0.0.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI= +github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec= github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -150,164 +642,474 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rubenv/sql-migrate v0.0.0-20190212093014-1007f53448d7/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/santhosh-tekuri/jsonschema/v2 v2.1.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= +github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= +github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0= +github.com/segmentio/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= +github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.elastic.co/apm v1.8.0/go.mod h1:tCw6CkOJgkWnzEthFN9HUP1uL3Gjc/Ur6m7gRPLaoH0= +go.elastic.co/apm/module/apmhttp v1.8.0/go.mod h1:9LPFlEON51/lRbnWDfqAWErihIiAFDUMfMV27YjoWQ8= +go.elastic.co/apm/module/apmot v1.8.0/go.mod h1:Q5Xzabte8G/fkvDjr1jlDuOSUt9hkVWNZEHh6ZNaTjI= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.13.0/go.mod h1:TwTkyRaTam1pOIb2wxcAiC2hkMVbokXkt6DEt5nDkD8= +go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190102171810-8d7daa0c54b3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181022134430-8a28ead16f52/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026064943-731415f00dce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181024171208-a2dc47679d30/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181105230042-78dc5bac0cac/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181107215632-34b416bd17b3/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181114190951-94339b83286c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181119130350-139d099f6620/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127195227-b4e97c0ed882/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181203210056-e5f3ab76ea4b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205224935-3576414c54a4/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181206194817-bcd4e47d0288/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181207183836-8bc39b988060/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181212172921-837e80568c09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190102213336-ca9055ed7d04/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190104182027-498d95493402/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190613204242-ed0dc450797f/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200203215610-ab391d50b528/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200721223218-6123e77877b2 h1:kxDWg8KNMtpGjI/XVKGgOtSljTnVg/PrjhS8+0pxjLE= golang.org/x/tools v0.0.0-20200721223218-6123e77877b2/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20191229114700-bbb4dff026f8/go.mod h1:2IgXn/sJaRbePPBA1wRj8OE+QLvVaH0q8SK6TSTKlnk= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.0.0-20200111075622-4abb28f724d5/go.mod h1:+HbaZVpsa73UwN7kXGCECULRHovLRJjH+t5cFPgxErs= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +gopkg.in/DataDog/dd-trace-go.v1 v1.27.0/go.mod h1:Sp1lku8WJMvNV0kjDI4Ni/T7J/U3BO5ct5kEaoVU8+I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/handler/oauth2/flow_authorize_code_auth.go b/handler/oauth2/flow_authorize_code_auth.go index 5e5b1a665..b63fa3989 100644 --- a/handler/oauth2/flow_authorize_code_auth.go +++ b/handler/oauth2/flow_authorize_code_auth.go @@ -27,7 +27,7 @@ import ( "strings" "time" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" ) @@ -77,19 +77,21 @@ func (c *AuthorizeExplicitGrantHandler) HandleAuthorizeEndpointRequest(ctx conte return nil } + ar.SetDefaultResponseMode(fosite.ResponseModeQuery) + // Disabled because this is already handled at the authorize_request_handler // if !ar.GetClient().GetResponseTypes().Has("code") { - // return errors.WithStack(fosite.ErrInvalidGrant) + // return errorsx.WithStack(fosite.ErrInvalidGrant) // } if !c.secureChecker()(ar.GetRedirectURI()) { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Redirect URL is using an insecure protocol, http is only allowed for hosts with suffix `localhost`, for example: http://myapp.localhost/.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Redirect URL is using an insecure protocol, http is only allowed for hosts with suffix `localhost`, for example: http://myapp.localhost/.")) } client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } @@ -103,17 +105,18 @@ func (c *AuthorizeExplicitGrantHandler) HandleAuthorizeEndpointRequest(ctx conte func (c *AuthorizeExplicitGrantHandler) IssueAuthorizeCode(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error { code, signature, err := c.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } ar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(c.AuthCodeLifespan)) if err := c.CoreStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.GetSanitationWhiteList())); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } - resp.AddQuery("code", code) - resp.AddQuery("state", ar.GetState()) - resp.AddQuery("scope", strings.Join(ar.GetGrantedScopes(), " ")) + resp.AddParameter("code", code) + resp.AddParameter("state", ar.GetState()) + resp.AddParameter("scope", strings.Join(ar.GetGrantedScopes(), " ")) + ar.SetResponseTypeHandled("code") return nil } diff --git a/handler/oauth2/flow_authorize_code_auth_test.go b/handler/oauth2/flow_authorize_code_auth_test.go index 198208137..5c9c20620 100644 --- a/handler/oauth2/flow_authorize_code_auth_test.go +++ b/handler/oauth2/flow_authorize_code_auth_test.go @@ -122,11 +122,12 @@ func TestAuthorizeCode_HandleAuthorizeEndpointRequest(t *testing.T) { }, description: "should pass", expect: func(t *testing.T, areq *fosite.AuthorizeRequest, aresp *fosite.AuthorizeResponse) { - code := aresp.GetQuery().Get("code") + code := aresp.GetParameters().Get("code") assert.NotEmpty(t, code) - assert.Equal(t, strings.Join(areq.GrantedScope, " "), aresp.GetQuery().Get("scope")) - assert.Equal(t, areq.State, aresp.GetQuery().Get("state")) + assert.Equal(t, strings.Join(areq.GrantedScope, " "), aresp.GetParameters().Get("scope")) + assert.Equal(t, areq.State, aresp.GetParameters().Get("state")) + assert.Equal(t, fosite.ResponseModeQuery, areq.GetResponseMode()) }, }, } { diff --git a/handler/oauth2/flow_authorize_code_token.go b/handler/oauth2/flow_authorize_code_token.go index 968a06a47..957619816 100644 --- a/handler/oauth2/flow_authorize_code_token.go +++ b/handler/oauth2/flow_authorize_code_token.go @@ -25,6 +25,8 @@ import ( "context" "time" + "github.com/ory/x/errorsx" + "github.com/ory/fosite/storage" "github.com/pkg/errors" @@ -38,11 +40,11 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C // grant_type REQUIRED. // Value MUST be set to "authorization_code". if !request.GetGrantTypes().ExactOne("authorization_code") { - return errors.WithStack(errors.WithStack(fosite.ErrUnknownRequest)) + return errorsx.WithStack(errorsx.WithStack(fosite.ErrUnknownRequest)) } if !request.GetClient().GetGrantTypes().Has("authorization_code") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) } code := request.GetRequestForm().Get("code") @@ -67,17 +69,17 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C hint += " Additionally, an error occurred during processing the refresh token revocation." debug += "Revocation of refresh_token lead to error " + revErr.Error() + "." } - return errors.WithStack(fosite.ErrInvalidGrant.WithHint(hint).WithDebug(debug)) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(hint).WithDebug(debug)) } else if err != nil && errors.Is(err, fosite.ErrNotFound) { - return errors.WithStack(fosite.ErrInvalidGrant.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) } else if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } // The authorization server MUST verify that the authorization code is valid // This needs to happen after store retrieval for the session to be hydrated properly if err := c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, request, code); err != nil { - return errors.WithStack(fosite.ErrInvalidGrant.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error())) } // Override scopes @@ -90,7 +92,7 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C // confidential client, or if the client is public, ensure that the // code was issued to "client_id" in the request, if authorizeRequest.GetClient().GetID() != request.GetClient().GetID() { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the one from the authorize request.")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the one from the authorize request.")) } // ensure that the "redirect_uri" parameter is present if the @@ -99,7 +101,7 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C // their values are identical. forcedRedirectURI := authorizeRequest.GetRequestForm().Get("redirect_uri") if forcedRedirectURI != "" && forcedRedirectURI != request.GetRequestForm().Get("redirect_uri") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request.")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request.")) } // Checking of POST client_id skipped, because: @@ -134,17 +136,17 @@ func (c *AuthorizeExplicitGrantHandler) PopulateTokenEndpointResponse(ctx contex // grant_type REQUIRED. // Value MUST be set to "authorization_code", as this is the explicit grant handler. if !requester.GetGrantTypes().ExactOne("authorization_code") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } code := requester.GetRequestForm().Get("code") signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(code) authorizeRequest, err := c.CoreStorage.GetAuthorizeCodeSession(ctx, signature, requester.GetSession()) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } else if err := c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, requester, code); err != nil { // This needs to happen after store retrieval for the session to be hydrated properly - return errors.WithStack(fosite.ErrInvalidRequest.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error())) } for _, scope := range authorizeRequest.GetGrantedScopes() { @@ -157,38 +159,38 @@ func (c *AuthorizeExplicitGrantHandler) PopulateTokenEndpointResponse(ctx contex access, accessSignature, err := c.AccessTokenStrategy.GenerateAccessToken(ctx, requester) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } var refresh, refreshSignature string if canIssueRefreshToken(c, authorizeRequest) { refresh, refreshSignature, err = c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } } ctx, err = storage.MaybeBeginTx(ctx, c.CoreStorage) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } if err := c.CoreStorage.InvalidateAuthorizeCodeSession(ctx, signature); err != nil { if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.CoreStorage); rollBackTxnErr != nil { - err = rollBackTxnErr + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr)) } - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } else if err := c.CoreStorage.CreateAccessTokenSession(ctx, accessSignature, requester.Sanitize([]string{})); err != nil { if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.CoreStorage); rollBackTxnErr != nil { - err = rollBackTxnErr + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr)) } - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } else if refreshSignature != "" { if err := c.CoreStorage.CreateRefreshTokenSession(ctx, refreshSignature, requester.Sanitize([]string{})); err != nil { if rollBackTxnErr := storage.MaybeRollbackTx(ctx, c.CoreStorage); rollBackTxnErr != nil { - err = rollBackTxnErr + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("error: %s; rollback error: %s", err, rollBackTxnErr)) } - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } } @@ -201,7 +203,7 @@ func (c *AuthorizeExplicitGrantHandler) PopulateTokenEndpointResponse(ctx contex } if err := storage.MaybeCommitTx(ctx, c.CoreStorage); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } return nil diff --git a/handler/oauth2/flow_authorize_implicit.go b/handler/oauth2/flow_authorize_implicit.go index 99e35ede8..44bdb6b47 100644 --- a/handler/oauth2/flow_authorize_implicit.go +++ b/handler/oauth2/flow_authorize_implicit.go @@ -27,7 +27,7 @@ import ( "strings" "time" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" ) @@ -53,19 +53,21 @@ func (c *AuthorizeImplicitGrantTypeHandler) HandleAuthorizeEndpointRequest(ctx c return nil } + ar.SetDefaultResponseMode(fosite.ResponseModeFragment) + // Disabled because this is already handled at the authorize_request_handler // if !ar.GetClient().GetResponseTypes().Has("token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type token")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type token")) // } if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } @@ -88,18 +90,18 @@ func (c *AuthorizeImplicitGrantTypeHandler) IssueImplicitAccessToken(ctx context // Generate the code token, signature, err := c.AccessTokenStrategy.GenerateAccessToken(ctx, ar) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } if err := c.AccessTokenStorage.CreateAccessTokenSession(ctx, signature, ar.Sanitize([]string{})); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } + resp.AddParameter("access_token", token) + resp.AddParameter("expires_in", strconv.FormatInt(int64(getExpiresIn(ar, fosite.AccessToken, c.AccessTokenLifespan, time.Now().UTC())/time.Second), 10)) + resp.AddParameter("token_type", "bearer") + resp.AddParameter("state", ar.GetState()) + resp.AddParameter("scope", strings.Join(ar.GetGrantedScopes(), " ")) - resp.AddFragment("access_token", token) - resp.AddFragment("expires_in", strconv.FormatInt(int64(getExpiresIn(ar, fosite.AccessToken, c.AccessTokenLifespan, time.Now().UTC())/time.Second), 10)) - resp.AddFragment("token_type", "bearer") - resp.AddFragment("state", ar.GetState()) - resp.AddFragment("scope", strings.Join(ar.GetGrantedScopes(), " ")) ar.SetResponseTypeHandled("token") return nil diff --git a/handler/oauth2/flow_authorize_implicit_test.go b/handler/oauth2/flow_authorize_implicit_test.go index d34c59202..6740bc7be 100644 --- a/handler/oauth2/flow_authorize_implicit_test.go +++ b/handler/oauth2/flow_authorize_implicit_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/require" @@ -36,21 +38,12 @@ import ( func TestAuthorizeImplicit_EndpointHandler(t *testing.T) { ctrl := gomock.NewController(t) - store := internal.NewMockAccessTokenStorage(ctrl) - chgen := internal.NewMockAccessTokenStrategy(ctrl) - aresp := internal.NewMockAuthorizeResponder(ctrl) defer ctrl.Finish() areq := fosite.NewAuthorizeRequest() areq.Session = new(fosite.DefaultSession) + h, store, chgen, aresp := makeAuthorizeImplicitGrantTypeHandler(ctrl) - h := AuthorizeImplicitGrantTypeHandler{ - AccessTokenStorage: store, - AccessTokenStrategy: chgen, - AccessTokenLifespan: time.Hour, - ScopeStrategy: fosite.HierarchicScopeStrategy, - AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, - } for k, c := range []struct { description string setup func() @@ -118,11 +111,11 @@ func TestAuthorizeImplicit_EndpointHandler(t *testing.T) { store.EXPECT().CreateAccessTokenSession(nil, "ats", gomock.Eq(areq.Sanitize([]string{}))).AnyTimes().Return(nil) - aresp.EXPECT().AddFragment("access_token", "access.ats") - aresp.EXPECT().AddFragment("expires_in", gomock.Any()) - aresp.EXPECT().AddFragment("token_type", "bearer") - aresp.EXPECT().AddFragment("state", "state") - aresp.EXPECT().AddFragment("scope", "scope") + aresp.EXPECT().AddParameter("access_token", "access.ats") + aresp.EXPECT().AddParameter("expires_in", gomock.Any()) + aresp.EXPECT().AddParameter("token_type", "bearer") + aresp.EXPECT().AddParameter("state", "state") + aresp.EXPECT().AddParameter("scope", "scope") }, expectErr: nil, }, @@ -138,3 +131,49 @@ func TestAuthorizeImplicit_EndpointHandler(t *testing.T) { }) } } +func makeAuthorizeImplicitGrantTypeHandler(ctrl *gomock.Controller) (AuthorizeImplicitGrantTypeHandler, + *internal.MockAccessTokenStorage, *internal.MockAccessTokenStrategy, *internal.MockAuthorizeResponder) { + store := internal.NewMockAccessTokenStorage(ctrl) + chgen := internal.NewMockAccessTokenStrategy(ctrl) + aresp := internal.NewMockAuthorizeResponder(ctrl) + + h := AuthorizeImplicitGrantTypeHandler{ + AccessTokenStorage: store, + AccessTokenStrategy: chgen, + AccessTokenLifespan: time.Hour, + ScopeStrategy: fosite.HierarchicScopeStrategy, + AudienceMatchingStrategy: fosite.DefaultAudienceMatchingStrategy, + } + + return h, store, chgen, aresp +} + +func TestDefaultResponseMode_AuthorizeImplicit_EndpointHandler(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + areq := fosite.NewAuthorizeRequest() + areq.Session = new(fosite.DefaultSession) + h, store, chgen, aresp := makeAuthorizeImplicitGrantTypeHandler(ctrl) + + areq.State = "state" + areq.GrantedScope = fosite.Arguments{"scope"} + areq.ResponseTypes = fosite.Arguments{"token"} + areq.Client = &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"implicit"}, + ResponseTypes: fosite.Arguments{"token"}, + } + + store.EXPECT().CreateAccessTokenSession(nil, "ats", gomock.Eq(areq.Sanitize([]string{}))).AnyTimes().Return(nil) + + aresp.EXPECT().AddParameter("access_token", "access.ats") + aresp.EXPECT().AddParameter("expires_in", gomock.Any()) + aresp.EXPECT().AddParameter("token_type", "bearer") + aresp.EXPECT().AddParameter("state", "state") + aresp.EXPECT().AddParameter("scope", "scope") + chgen.EXPECT().GenerateAccessToken(nil, areq).AnyTimes().Return("access.ats", "ats", nil) + + err := h.HandleAuthorizeEndpointRequest(nil, areq, aresp) + assert.NoError(t, err) + assert.Equal(t, fosite.ResponseModeFragment, areq.GetResponseMode()) +} diff --git a/handler/oauth2/flow_client_credentials.go b/handler/oauth2/flow_client_credentials.go index 385c64fdc..01bfea127 100644 --- a/handler/oauth2/flow_client_credentials.go +++ b/handler/oauth2/flow_client_credentials.go @@ -25,7 +25,7 @@ import ( "context" "time" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" ) @@ -41,13 +41,13 @@ func (c *ClientCredentialsGrantHandler) HandleTokenEndpointRequest(_ context.Con // grant_type REQUIRED. // Value MUST be set to "client_credentials". if !request.GetGrantTypes().ExactOne("client_credentials") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } client := request.GetClient() for _, scope := range request.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } @@ -59,7 +59,7 @@ func (c *ClientCredentialsGrantHandler) HandleTokenEndpointRequest(_ context.Con // This requirement is already fulfilled because fosite requires all token requests to be authenticated as described // in https://tools.ietf.org/html/rfc6749#section-3.2.1 if client.IsPublic() { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant \"client_credentials\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant 'client_credentials'.")) } // if the client is not public, he has already been authenticated by the access request handler. @@ -70,11 +70,11 @@ func (c *ClientCredentialsGrantHandler) HandleTokenEndpointRequest(_ context.Con // PopulateTokenEndpointResponse implements https://tools.ietf.org/html/rfc6749#section-4.4.3 func (c *ClientCredentialsGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, request fosite.AccessRequester, response fosite.AccessResponder) error { if !request.GetGrantTypes().ExactOne("client_credentials") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !request.GetClient().GetGrantTypes().Has("client_credentials") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"client_credentials\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'client_credentials'.")) } return c.IssueAccessToken(ctx, request, response) diff --git a/handler/oauth2/flow_refresh.go b/handler/oauth2/flow_refresh.go index 18d93f091..17c3036cc 100644 --- a/handler/oauth2/flow_refresh.go +++ b/handler/oauth2/flow_refresh.go @@ -27,6 +27,8 @@ import ( "strings" "time" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -54,36 +56,36 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex // grant_type REQUIRED. // Value MUST be set to "refresh_token". if !request.GetGrantTypes().ExactOne("refresh_token") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !request.GetClient().GetGrantTypes().Has("refresh_token") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"refresh_token\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'refresh_token'.")) } refresh := request.GetRequestForm().Get("refresh_token") signature := c.RefreshTokenStrategy.RefreshTokenSignature(refresh) originalRequest, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, request.GetSession()) if errors.Is(err, fosite.ErrNotFound) { - return errors.WithStack(fosite.ErrInvalidGrant.WithCause(err).WithDebugf("The refresh token has not been found: %s", err.Error())) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebugf("The refresh token has not been found: %s", err.Error())) } else if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } else if err := c.RefreshTokenStrategy.ValidateRefreshToken(ctx, originalRequest, refresh); err != nil { // The authorization server MUST ... validate the refresh token. // This needs to happen after store retrieval for the session to be hydrated properly - return errors.WithStack(fosite.ErrInvalidRequest.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithWrap(err).WithDebug(err.Error())) } if !(len(c.RefreshTokenScopes) == 0 || originalRequest.GetGrantedScopes().HasOneOf(c.RefreshTokenScopes...)) { scopeNames := strings.Join(c.RefreshTokenScopes, " or ") - hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the \"refresh_token\" authorization grant.", scopeNames) - return errors.WithStack(fosite.ErrScopeNotGranted.WithHint(hint)) + hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the 'refresh_token' authorization grant.", scopeNames) + return errorsx.WithStack(fosite.ErrScopeNotGranted.WithHint(hint)) } // The authorization server MUST ... and ensure that the refresh token was issued to the authenticated client if originalRequest.GetClient().GetID() != request.GetClient().GetID() { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the ID during the initial token issuance.")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the ID during the initial token issuance.")) } request.SetSession(originalRequest.GetSession().Clone()) @@ -92,7 +94,7 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex for _, scope := range originalRequest.GetGrantedScopes() { if !c.ScopeStrategy(request.GetClient().GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } request.GrantScope(scope) } @@ -116,24 +118,24 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex // PopulateTokenEndpointResponse implements https://tools.ietf.org/html/rfc6749#section-6 func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { if !requester.GetGrantTypes().ExactOne("refresh_token") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } accessToken, accessSignature, err := c.AccessTokenStrategy.GenerateAccessToken(ctx, requester) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } refreshToken, refreshSignature, err := c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } signature := c.RefreshTokenStrategy.RefreshTokenSignature(requester.GetRequestForm().Get("refresh_token")) ctx, err = storage.MaybeBeginTx(ctx, c.TokenRevocationStorage) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } ts, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, nil) @@ -173,22 +175,22 @@ func handleRefreshTokenEndpointResponseStorageError(ctx context.Context, rollbac defer func() { if rollback { if rbErr := storage.MaybeRollbackTx(ctx, store); rbErr != nil { - err = errors.WithStack(fosite.ErrServerError.WithCause(rbErr).WithDebug(rbErr.Error())) + err = errorsx.WithStack(fosite.ErrServerError.WithWrap(rbErr).WithDebug(rbErr.Error())) } } }() if errors.Is(storageErr, fosite.ErrSerializationFailure) { - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithDebugf(storageErr.Error()). WithHint("Failed to refresh token because of multiple concurrent requests using the same token which is not allowed.")) } if errors.Is(storageErr, fosite.ErrNotFound) { - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithDebugf(storageErr.Error()). WithHint("Failed to refresh token because of multiple concurrent requests using the same token which is not allowed.")) } - return errors.WithStack(fosite.ErrServerError.WithCause(storageErr).WithDebug(storageErr.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(storageErr).WithDebug(storageErr.Error())) } diff --git a/handler/oauth2/flow_resource_owner.go b/handler/oauth2/flow_resource_owner.go index 8f216b51e..f0e49a51c 100644 --- a/handler/oauth2/flow_resource_owner.go +++ b/handler/oauth2/flow_resource_owner.go @@ -25,6 +25,8 @@ import ( "context" "time" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -47,17 +49,17 @@ func (c *ResourceOwnerPasswordCredentialsGrantHandler) HandleTokenEndpointReques // grant_type REQUIRED. // Value MUST be set to "password". if !request.GetGrantTypes().ExactOne("password") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !request.GetClient().GetGrantTypes().Has("password") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The client is not allowed to use authorization grant \"password\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The client is not allowed to use authorization grant 'password'.")) } client := request.GetClient() for _, scope := range request.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } @@ -68,11 +70,11 @@ func (c *ResourceOwnerPasswordCredentialsGrantHandler) HandleTokenEndpointReques username := request.GetRequestForm().Get("username") password := request.GetRequestForm().Get("password") if username == "" || password == "" { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Username or password are missing from the POST body.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Username or password are missing from the POST body.")) } else if err := c.ResourceOwnerPasswordCredentialsGrantStorage.Authenticate(ctx, username, password); errors.Is(err, fosite.ErrNotFound) { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("Unable to authenticate the provided username and password credentials.").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("Unable to authenticate the provided username and password credentials.").WithWrap(err).WithDebug(err.Error())) } else if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } // Credentials must not be passed around, potentially leaking to the database! @@ -89,7 +91,7 @@ func (c *ResourceOwnerPasswordCredentialsGrantHandler) HandleTokenEndpointReques // PopulateTokenEndpointResponse implements https://tools.ietf.org/html/rfc6749#section-4.3.3 func (c *ResourceOwnerPasswordCredentialsGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { if !requester.GetGrantTypes().ExactOne("password") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } var refresh, refreshSignature string @@ -97,9 +99,9 @@ func (c *ResourceOwnerPasswordCredentialsGrantHandler) PopulateTokenEndpointResp var err error refresh, refreshSignature, err = c.RefreshTokenStrategy.GenerateRefreshToken(ctx, requester) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } else if err := c.ResourceOwnerPasswordCredentialsGrantStorage.CreateRefreshTokenSession(ctx, refreshSignature, requester.Sanitize([]string{})); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } } diff --git a/handler/oauth2/introspector.go b/handler/oauth2/introspector.go index d5f0bf743..cd5bed166 100644 --- a/handler/oauth2/introspector.go +++ b/handler/oauth2/introspector.go @@ -24,7 +24,7 @@ package oauth2 import ( "context" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" ) @@ -71,7 +71,7 @@ func matchScopes(ss fosite.ScopeStrategy, granted, scopes []string) error { } if !ss(granted, scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The request scope \"%s\" has not been granted or is not allowed to be requested.", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The request scope '%s' has not been granted or is not allowed to be requested.", scope)) } } @@ -82,7 +82,7 @@ func (c *CoreValidator) introspectAccessToken(ctx context.Context, token string, sig := c.CoreStrategy.AccessTokenSignature(token) or, err := c.CoreStorage.GetAccessTokenSession(ctx, sig, accessRequest.GetSession()) if err != nil { - return errors.WithStack(fosite.ErrRequestUnauthorized.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrRequestUnauthorized.WithWrap(err).WithDebug(err.Error())) } else if err := c.CoreStrategy.ValidateAccessToken(ctx, or, token); err != nil { return err } @@ -100,7 +100,7 @@ func (c *CoreValidator) introspectRefreshToken(ctx context.Context, token string or, err := c.CoreStorage.GetRefreshTokenSession(ctx, sig, accessRequest.GetSession()) if err != nil { - return errors.WithStack(fosite.ErrRequestUnauthorized.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrRequestUnauthorized.WithWrap(err).WithDebug(err.Error())) } else if err := c.CoreStrategy.ValidateRefreshToken(ctx, or, token); err != nil { return err } diff --git a/handler/oauth2/introspector_jwt.go b/handler/oauth2/introspector_jwt.go index 829c633fa..b90ccfa6e 100644 --- a/handler/oauth2/introspector_jwt.go +++ b/handler/oauth2/introspector_jwt.go @@ -23,38 +23,84 @@ package oauth2 import ( "context" + "time" - "github.com/pkg/errors" + jwtx "github.com/dgrijalva/jwt-go" "github.com/ory/fosite" + "github.com/ory/fosite/token/jwt" ) -type JWTAccessTokenStrategy interface { - AccessTokenStrategy - JWTStrategy -} - type StatelessJWTValidator struct { - JWTAccessTokenStrategy + jwt.JWTStrategy ScopeStrategy fosite.ScopeStrategy } +// AccessTokenJWTToRequest tries to reconstruct fosite.Request from a JWT. +func AccessTokenJWTToRequest(token *jwtx.Token) fosite.Requester { + mapClaims := token.Claims.(jwtx.MapClaims) + claims := jwt.JWTClaims{} + claims.FromMapClaims(mapClaims) + + requestedAt := claims.IssuedAt + requestedAtClaim, ok := mapClaims["rat"] + if ok { + switch requestedAtClaim.(type) { + case float64: + requestedAt = time.Unix(int64(requestedAtClaim.(float64)), 0).UTC() + case int64: + requestedAt = time.Unix(requestedAtClaim.(int64), 0).UTC() + } + } + + clientId := "" + clientIdClaim, ok := mapClaims["client_id"] + if ok { + switch clientIdClaim.(type) { + case string: + clientId = clientIdClaim.(string) + } + } + + return &fosite.Request{ + RequestedAt: requestedAt, + Client: &fosite.DefaultClient{ + ID: clientId, + }, + // We do not really know which scopes were requested, so we set them to granted. + RequestedScope: claims.Scope, + GrantedScope: claims.Scope, + Session: &JWTSession{ + JWTClaims: &claims, + JWTHeader: &jwt.Headers{ + Extra: token.Header, + }, + ExpiresAt: map[fosite.TokenType]time.Time{ + fosite.AccessToken: claims.ExpiresAt, + }, + Subject: claims.Subject, + }, + // We do not really know which audiences were requested, so we set them to granted. + RequestedAudience: claims.Audience, + GrantedAudience: claims.Audience, + } +} + func (v *StatelessJWTValidator) IntrospectToken(ctx context.Context, token string, tokenUse fosite.TokenUse, accessRequest fosite.AccessRequester, scopes []string) (fosite.TokenUse, error) { - or, err := v.JWTAccessTokenStrategy.ValidateJWT(ctx, fosite.AccessToken, token) + t, err := validate(ctx, v.JWTStrategy, token) if err != nil { return "", err } - for _, scope := range scopes { - if scope == "" { - continue - } + // TODO: From here we assume it is an access token, but how do we know it is really and that is not an ID token? - if !v.ScopeStrategy(or.GetGrantedScopes(), scope) { - return "", errors.WithStack(fosite.ErrInvalidScope) - } + requester := AccessTokenJWTToRequest(t) + + if err := matchScopes(v.ScopeStrategy, requester.GetGrantedScopes(), scopes); err != nil { + return fosite.AccessToken, err } - accessRequest.Merge(or) + accessRequest.Merge(requester) + return fosite.AccessToken, nil } diff --git a/handler/oauth2/introspector_jwt_test.go b/handler/oauth2/introspector_jwt_test.go index a05eac672..38a89add3 100644 --- a/handler/oauth2/introspector_jwt_test.go +++ b/handler/oauth2/introspector_jwt_test.go @@ -43,8 +43,8 @@ func TestIntrospectJWT(t *testing.T) { } v := &StatelessJWTValidator{ - JWTAccessTokenStrategy: strat, - ScopeStrategy: fosite.HierarchicScopeStrategy, + JWTStrategy: strat, + ScopeStrategy: fosite.HierarchicScopeStrategy, } for k, c := range []struct { @@ -137,7 +137,7 @@ func BenchmarkIntrospectJWT(b *testing.B) { } v := &StatelessJWTValidator{ - JWTAccessTokenStrategy: strat, + JWTStrategy: strat, } jwt := jwtValidCase(fosite.AccessToken) diff --git a/handler/oauth2/introspector_test.go b/handler/oauth2/introspector_test.go index b6ed4cda2..272a084e4 100644 --- a/handler/oauth2/introspector_test.go +++ b/handler/oauth2/introspector_test.go @@ -26,6 +26,8 @@ import ( "net/http" "testing" + "github.com/ory/x/errorsx" + "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -80,7 +82,7 @@ func TestIntrospectToken(t *testing.T) { description: "should fail because validation fails", setup: func() { store.EXPECT().GetAccessTokenSession(nil, "asdf", nil).AnyTimes().Return(areq, nil) - chgen.EXPECT().ValidateAccessToken(nil, areq, "1234").Return(errors.WithStack(fosite.ErrTokenExpired)) + chgen.EXPECT().ValidateAccessToken(nil, areq, "1234").Return(errorsx.WithStack(fosite.ErrTokenExpired)) chgen.EXPECT().RefreshTokenSignature("1234").Return("asdf") store.EXPECT().GetRefreshTokenSession(nil, "asdf", nil).Return(nil, errors.New("")) }, @@ -90,7 +92,7 @@ func TestIntrospectToken(t *testing.T) { description: "should fail because access token invalid", setup: func() { v.DisableRefreshTokenValidation = true - chgen.EXPECT().ValidateAccessToken(nil, areq, "1234").Return(errors.WithStack(fosite.ErrInvalidTokenFormat)) + chgen.EXPECT().ValidateAccessToken(nil, areq, "1234").Return(errorsx.WithStack(fosite.ErrInvalidTokenFormat)) }, expectErr: fosite.ErrInvalidTokenFormat, }, diff --git a/handler/oauth2/revocation.go b/handler/oauth2/revocation.go index aef9bcbfb..5b77013cc 100644 --- a/handler/oauth2/revocation.go +++ b/handler/oauth2/revocation.go @@ -24,6 +24,8 @@ package oauth2 import ( "context" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -67,7 +69,7 @@ func (r *TokenRevocationHandler) RevokeToken(ctx context.Context, token string, } if ar.GetClient().GetID() != client.GetID() { - return errors.WithStack(fosite.ErrUnauthorizedClient) + return errorsx.WithStack(fosite.ErrUnauthorizedClient) } requestID := ar.GetID() @@ -84,5 +86,5 @@ func storeErrorsToRevocationError(err1, err2 error) error { } // there was an unexpected error => the token may still exist and the client should retry later - return errors.WithStack(fosite.ErrTemporarilyUnavailable) + return errorsx.WithStack(fosite.ErrTemporarilyUnavailable) } diff --git a/handler/oauth2/strategy.go b/handler/oauth2/strategy.go index ec091a323..b4a911fd8 100644 --- a/handler/oauth2/strategy.go +++ b/handler/oauth2/strategy.go @@ -33,10 +33,6 @@ type CoreStrategy interface { AuthorizeCodeStrategy } -type JWTStrategy interface { - ValidateJWT(ctx context.Context, tokenType fosite.TokenType, token string) (requester fosite.Requester, err error) -} - type AccessTokenStrategy interface { AccessTokenSignature(token string) string GenerateAccessToken(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) diff --git a/handler/oauth2/strategy_hmacsha.go b/handler/oauth2/strategy_hmacsha.go index 16d962b66..fd2b85491 100644 --- a/handler/oauth2/strategy_hmacsha.go +++ b/handler/oauth2/strategy_hmacsha.go @@ -25,7 +25,7 @@ import ( "context" "time" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" enigma "github.com/ory/fosite/token/hmac" @@ -55,10 +55,10 @@ func (h HMACSHAStrategy) GenerateAccessToken(_ context.Context, _ fosite.Request func (h HMACSHAStrategy) ValidateAccessToken(_ context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AccessToken) if exp.IsZero() && r.GetRequestedAt().Add(h.AccessTokenLifespan).Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at \"%s\".", r.GetRequestedAt().Add(h.AccessTokenLifespan))) + return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", r.GetRequestedAt().Add(h.AccessTokenLifespan))) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at \"%s\".", exp)) + return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp)) } return h.Enigma.Validate(token) } @@ -74,7 +74,7 @@ func (h HMACSHAStrategy) ValidateRefreshToken(_ context.Context, r fosite.Reques return h.Enigma.Validate(token) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at \"%s\".", exp)) + return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp)) } return h.Enigma.Validate(token) } @@ -86,10 +86,10 @@ func (h HMACSHAStrategy) GenerateAuthorizeCode(_ context.Context, _ fosite.Reque func (h HMACSHAStrategy) ValidateAuthorizeCode(_ context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode) if exp.IsZero() && r.GetRequestedAt().Add(h.AuthorizeCodeLifespan).Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at \"%s\".", r.GetRequestedAt().Add(h.AuthorizeCodeLifespan))) + return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", r.GetRequestedAt().Add(h.AuthorizeCodeLifespan))) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { - return errors.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at \"%s\".", exp)) + return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp)) } return h.Enigma.Validate(token) diff --git a/handler/oauth2/strategy_jwt.go b/handler/oauth2/strategy_jwt.go index 902bbb2f9..2234fdac0 100644 --- a/handler/oauth2/strategy_jwt.go +++ b/handler/oauth2/strategy_jwt.go @@ -26,6 +26,8 @@ import ( "strings" "time" + "github.com/ory/x/errorsx" + jwtx "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" @@ -69,41 +71,10 @@ func (h *DefaultJWTStrategy) GenerateAccessToken(ctx context.Context, requester } func (h *DefaultJWTStrategy) ValidateAccessToken(ctx context.Context, _ fosite.Requester, token string) error { - _, err := h.validate(ctx, token) + _, err := validate(ctx, h.JWTStrategy, token) return err } -func (h *DefaultJWTStrategy) ValidateJWT(ctx context.Context, tokenType fosite.TokenType, token string) (requester fosite.Requester, err error) { - t, err := h.validate(ctx, token) - if err != nil { - return nil, err - } - - claims := jwt.JWTClaims{ - ScopeField: h.ScopeField, - } - claims.FromMapClaims(t.Claims.(jwtx.MapClaims)) - - requester = &fosite.Request{ - Client: &fosite.DefaultClient{}, - RequestedAt: claims.IssuedAt, - Session: &JWTSession{ - JWTClaims: &claims, - JWTHeader: &jwt.Headers{ - Extra: make(map[string]interface{}), - }, - ExpiresAt: map[fosite.TokenType]time.Time{ - tokenType: claims.ExpiresAt, - }, - Subject: claims.Subject, - }, - RequestedScope: claims.Scope, - GrantedScope: claims.Scope, - } - - return -} - func (h DefaultJWTStrategy) RefreshTokenSignature(token string) string { return h.HMACSHAStrategy.RefreshTokenSignature(token) } @@ -128,8 +99,8 @@ func (h *DefaultJWTStrategy) ValidateAuthorizeCode(ctx context.Context, req fosi return h.HMACSHAStrategy.ValidateAuthorizeCode(ctx, req, token) } -func (h *DefaultJWTStrategy) validate(ctx context.Context, token string) (t *jwtx.Token, err error) { - t, err = h.JWTStrategy.Decode(ctx, token) +func validate(ctx context.Context, jwtStrategy jwt.JWTStrategy, token string) (t *jwtx.Token, err error) { + t, err = jwtStrategy.Decode(ctx, token) if err == nil { err = t.Claims.Valid() @@ -140,27 +111,27 @@ func (h *DefaultJWTStrategy) validate(ctx context.Context, token string) (t *jwt if errors.As(err, &e) { switch e.Errors { case jwtx.ValidationErrorMalformed: - err = errors.WithStack(fosite.ErrInvalidTokenFormat.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrInvalidTokenFormat.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorUnverifiable: - err = errors.WithStack(fosite.ErrTokenSignatureMismatch.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenSignatureMismatch.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorSignatureInvalid: - err = errors.WithStack(fosite.ErrTokenSignatureMismatch.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenSignatureMismatch.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorAudience: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorExpired: - err = errors.WithStack(fosite.ErrTokenExpired.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenExpired.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorIssuedAt: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorIssuer: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorNotValidYet: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorId: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) case jwtx.ValidationErrorClaimsInvalid: - err = errors.WithStack(fosite.ErrTokenClaim.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrTokenClaim.WithWrap(err).WithDebug(err.Error())) default: - err = errors.WithStack(fosite.ErrRequestUnauthorized.WithCause(err).WithDebug(err.Error())) + err = errorsx.WithStack(fosite.ErrRequestUnauthorized.WithWrap(err).WithDebug(err.Error())) } } } diff --git a/handler/openid/flow_explicit_auth.go b/handler/openid/flow_explicit_auth.go index 89c6e2f5f..1bbd38e9c 100644 --- a/handler/openid/flow_explicit_auth.go +++ b/handler/openid/flow_explicit_auth.go @@ -24,7 +24,7 @@ package openid import ( "context" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" ) @@ -51,11 +51,11 @@ func (c *OpenIDConnectExplicitHandler) HandleAuthorizeEndpointRequest(ctx contex } //if !ar.GetClient().GetResponseTypes().Has("id_token", "code") { - // return errors.WithStack(fosite.ErrInvalidRequest.WithDebug("The client is not allowed to use response type id_token and code")) + // return errorsx.WithStack(fosite.ErrInvalidRequest.WithDebug("The client is not allowed to use response type id_token and code")) //} if len(resp.GetCode()) == 0 { - return errors.WithStack(fosite.ErrMisconfiguration.WithDebug("The authorization code has not been issued yet, indicating a broken code configuration.")) + return errorsx.WithStack(fosite.ErrMisconfiguration.WithDebug("The authorization code has not been issued yet, indicating a broken code configuration.")) } if err := c.OpenIDConnectRequestValidator.ValidatePrompt(ctx, ar); err != nil { @@ -63,7 +63,7 @@ func (c *OpenIDConnectExplicitHandler) HandleAuthorizeEndpointRequest(ctx contex } if err := c.OpenIDConnectRequestStorage.CreateOpenIDConnectSession(ctx, resp.GetCode(), ar.Sanitize(oidcParameters)); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } // there is no need to check for https, because it has already been checked by core.explicit diff --git a/handler/openid/flow_explicit_token.go b/handler/openid/flow_explicit_token.go index cf933d5cc..376680b40 100644 --- a/handler/openid/flow_explicit_token.go +++ b/handler/openid/flow_explicit_token.go @@ -24,43 +24,45 @@ package openid import ( "context" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" ) func (c *OpenIDConnectExplicitHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } func (c *OpenIDConnectExplicitHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { if !requester.GetGrantTypes().ExactOne("authorization_code") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } authorize, err := c.OpenIDConnectRequestStorage.GetOpenIDConnectSession(ctx, requester.GetRequestForm().Get("code"), requester) if errors.Is(err, ErrNoSessionFound) { - return errors.WithStack(fosite.ErrUnknownRequest.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrUnknownRequest.WithWrap(err).WithDebug(err.Error())) } else if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } if !authorize.GetGrantedScopes().Has("openid") { - return errors.WithStack(fosite.ErrMisconfiguration.WithDebug("An OpenID Connect session was found but the openid scope is missing, probably due to a broken code configuration.")) + return errorsx.WithStack(fosite.ErrMisconfiguration.WithDebug("An OpenID Connect session was found but the openid scope is missing, probably due to a broken code configuration.")) } if !requester.GetClient().GetGrantTypes().Has("authorization_code") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"authorization_code\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"authorization_code\".")) } sess, ok := requester.GetSession().(Session) if !ok { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) } claims := sess.IDTokenClaims() if claims.Subject == "" { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) } claims.AccessTokenHash = c.GetAccessTokenHash(ctx, requester, responder) @@ -69,7 +71,7 @@ func (c *OpenIDConnectExplicitHandler) PopulateTokenEndpointResponse(ctx context // https://openid.net/specs/openid-connect-registration-1_0.html // // if !requester.GetClient().GetResponseTypes().Has("id_token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type id_token")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type id_token")) // } return c.IssueExplicitIDToken(ctx, authorize, responder) diff --git a/handler/openid/flow_hybrid.go b/handler/openid/flow_hybrid.go index b15636657..df0f2f647 100644 --- a/handler/openid/flow_hybrid.go +++ b/handler/openid/flow_hybrid.go @@ -26,7 +26,7 @@ import ( "encoding/base64" "time" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" "github.com/ory/fosite/handler/oauth2" @@ -55,24 +55,33 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. return nil } + ar.SetDefaultResponseMode(fosite.ResponseModeFragment) + // Disabled because this is already handled at the authorize_request_handler //if ar.GetResponseTypes().Matches("token") && !ar.GetClient().GetResponseTypes().Has("token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the token response type")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the token response type")) //} else if ar.GetResponseTypes().Matches("code") && !ar.GetClient().GetResponseTypes().Has("code") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the code response type")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the code response type")) //} else if ar.GetResponseTypes().Matches("id_token") && !ar.GetClient().GetResponseTypes().Has("id_token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the id_token response type")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use the id_token response type")) //} - if nonce := ar.GetRequestForm().Get("nonce"); len(nonce) == 0 { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"nonce\" must be set when using the OpenID Connect Hybrid Flow.")) - } else if len(nonce) < c.MinParameterEntropy { - return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) + // The nonce is actually not required for hybrid flows. It fails the OpenID Connect Conformity + // Test Module "oidcc-ensure-request-without-nonce-succeeds-for-code-flow" if enabled. + // + nonce := ar.GetRequestForm().Get("nonce") + + if len(nonce) == 0 && ar.GetResponseTypes().Has("id_token") { + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'nonce' must be set when requesting an ID Token using the OpenID Connect Hybrid Flow.")) + } + + if len(nonce) > 0 && len(nonce) < c.MinParameterEntropy { + return errorsx.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) } sess, ok := ar.GetSession().(Session) if !ok { - return errors.WithStack(ErrInvalidSession) + return errorsx.WithStack(ErrInvalidSession) } if err := c.OpenIDConnectRequestValidator.ValidatePrompt(ctx, ar); err != nil { @@ -82,19 +91,19 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } claims := sess.IDTokenClaims() if ar.GetResponseTypes().Has("code") { if !ar.GetClient().GetGrantTypes().Has("authorization_code") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'authorization_code'.")) } code, signature, err := c.AuthorizeExplicitGrantHandler.AuthorizeCodeStrategy.GenerateAuthorizeCode(ctx, ar) if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } // This is not required because the auth code flow is being handled by oauth2/flow_authorize_code_token which in turn @@ -107,13 +116,13 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. // This is required because we must limit the authorize code lifespan. ar.GetSession().SetExpiresAt(fosite.AuthorizeCode, time.Now().UTC().Add(c.AuthorizeExplicitGrantHandler.AuthCodeLifespan).Round(time.Second)) if err := c.AuthorizeExplicitGrantHandler.CoreStorage.CreateAuthorizeCodeSession(ctx, signature, ar.Sanitize(c.AuthorizeExplicitGrantHandler.GetSanitationWhiteList())); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } - resp.AddFragment("code", code) + resp.AddParameter("code", code) ar.SetResponseTypeHandled("code") - hash, err := c.Enigma.Hash(ctx, []byte(resp.GetFragment().Get("code"))) + hash, err := c.Enigma.Hash(ctx, []byte(resp.GetParameters().Get("code"))) if err != nil { return err } @@ -121,28 +130,28 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. if ar.GetGrantedScopes().Has("openid") { if err := c.OpenIDConnectRequestStorage.CreateOpenIDConnectSession(ctx, resp.GetCode(), ar.Sanitize(oidcParameters)); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } } } if ar.GetResponseTypes().Has("token") { if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } else if err := c.AuthorizeImplicitGrantTypeHandler.IssueImplicitAccessToken(ctx, ar, resp); err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } ar.SetResponseTypeHandled("token") - hash, err := c.Enigma.Hash(ctx, []byte(resp.GetFragment().Get("access_token"))) + hash, err := c.Enigma.Hash(ctx, []byte(resp.GetParameters().Get("access_token"))) if err != nil { return err } claims.AccessTokenHash = base64.RawURLEncoding.EncodeToString([]byte(hash[:c.Enigma.GetSigningMethodLength()/2])) } - if resp.GetFragment().Get("state") == "" { - resp.AddFragment("state", ar.GetState()) + if resp.GetParameters().Get("state") == "" { + resp.AddParameter("state", ar.GetState()) } if !ar.GetGrantedScopes().Has("openid") || !ar.GetResponseTypes().Has("id_token") { @@ -151,7 +160,7 @@ func (c *OpenIDConnectHybridHandler) HandleAuthorizeEndpointRequest(ctx context. } if err := c.IDTokenHandleHelper.IssueImplicitIDToken(ctx, ar, resp); err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } ar.SetResponseTypeHandled("id_token") diff --git a/handler/openid/flow_hybrid_test.go b/handler/openid/flow_hybrid_test.go index 7d0ebeea8..db9ab2f78 100644 --- a/handler/openid/flow_hybrid_test.go +++ b/handler/openid/flow_hybrid_test.go @@ -130,20 +130,6 @@ func TestHybrid_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) }, }, - { - description: "should fail because nonce not set but required", - setup: func() OpenIDConnectHybridHandler { - areq.ResponseTypes = fosite.Arguments{"token", "code"} - areq.Client = &fosite.DefaultClient{ - GrantTypes: fosite.Arguments{"authorization_code", "implicit"}, - ResponseTypes: fosite.Arguments{"token", "code", "id_token"}, - Scopes: []string{"openid"}, - } - areq.GrantedScope = fosite.Arguments{"openid"} - return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) - }, - expectErr: fosite.ErrInvalidRequest, - }, { description: "should fail because nonce set but too short", setup: func() OpenIDConnectHybridHandler { @@ -221,6 +207,17 @@ func TestHybrid_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) }, }, + { + description: "should pass even if nonce was not set", + setup: func() OpenIDConnectHybridHandler { + areq.Client = &fosite.DefaultClient{ + GrantTypes: fosite.Arguments{"authorization_code", "implicit"}, + ResponseTypes: fosite.Arguments{"token", "code", "id_token"}, + Scopes: []string{"openid"}, + } + return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) + }, + }, { description: "should pass because nonce was set with low entropy but also with low min entropy", setup: func() OpenIDConnectHybridHandler { @@ -249,9 +246,22 @@ func TestHybrid_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) }, check: func() { - assert.NotEmpty(t, aresp.GetFragment().Get("id_token")) - assert.NotEmpty(t, aresp.GetFragment().Get("code")) - assert.NotEmpty(t, aresp.GetFragment().Get("access_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("code")) + assert.NotEmpty(t, aresp.GetParameters().Get("access_token")) + assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AuthorizeCode)) + }, + }, + { + description: "Default responseMode check", + setup: func() OpenIDConnectHybridHandler { + return makeOpenIDConnectHybridHandler(fosite.MinParameterEntropy) + }, + check: func() { + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("code")) + assert.NotEmpty(t, aresp.GetParameters().Get("access_token")) + assert.Equal(t, fosite.ResponseModeFragment, areq.GetResponseMode()) assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AuthorizeCode)) }, }, diff --git a/handler/openid/flow_implicit.go b/handler/openid/flow_implicit.go index 3f267b349..f3e0639e1 100644 --- a/handler/openid/flow_implicit.go +++ b/handler/openid/flow_implicit.go @@ -25,7 +25,7 @@ import ( "context" "encoding/base64" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" "github.com/ory/fosite" "github.com/ory/fosite/handler/oauth2" @@ -51,33 +51,35 @@ func (c *OpenIDConnectImplicitHandler) HandleAuthorizeEndpointRequest(ctx contex return nil } + ar.SetDefaultResponseMode(fosite.ResponseModeFragment) + if !ar.GetClient().GetGrantTypes().Has("implicit") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"implicit\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant 'implicit'.")) } // Disabled because this is already handled at the authorize_request_handler //if ar.GetResponseTypes().ExactOne("id_token") && !ar.GetClient().GetResponseTypes().Has("id_token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type id_token")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type id_token")) //} else if ar.GetResponseTypes().Matches("token", "id_token") && !ar.GetClient().GetResponseTypes().Has("token", "id_token") { - // return errors.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type token and id_token")) + // return errorsx.WithStack(fosite.ErrInvalidGrant.WithDebug("The client is not allowed to use response type token and id_token")) //} if nonce := ar.GetRequestForm().Get("nonce"); len(nonce) == 0 { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"nonce\" must be set when using the OpenID Connect Implicit Flow.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'nonce' must be set when using the OpenID Connect Implicit Flow.")) } else if len(nonce) < c.MinParameterEntropy { - return errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) + return errorsx.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", c.MinParameterEntropy)) } client := ar.GetClient() for _, scope := range ar.GetRequestedScopes() { if !c.ScopeStrategy(client.GetScopes(), scope) { - return errors.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope \"%s\".", scope)) + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } } sess, ok := ar.GetSession().(Session) if !ok { - return errors.WithStack(ErrInvalidSession) + return errorsx.WithStack(ErrInvalidSession) } if err := c.OpenIDConnectRequestValidator.ValidatePrompt(ctx, ar); err != nil { @@ -87,22 +89,22 @@ func (c *OpenIDConnectImplicitHandler) HandleAuthorizeEndpointRequest(ctx contex claims := sess.IDTokenClaims() if ar.GetResponseTypes().Has("token") { if err := c.AuthorizeImplicitGrantTypeHandler.IssueImplicitAccessToken(ctx, ar, resp); err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } ar.SetResponseTypeHandled("token") - hash, err := c.RS256JWTStrategy.Hash(ctx, []byte(resp.GetFragment().Get("access_token"))) + hash, err := c.RS256JWTStrategy.Hash(ctx, []byte(resp.GetParameters().Get("access_token"))) if err != nil { return err } claims.AccessTokenHash = base64.RawURLEncoding.EncodeToString([]byte(hash[:c.RS256JWTStrategy.GetSigningMethodLength()/2])) } else { - resp.AddFragment("state", ar.GetState()) + resp.AddParameter("state", ar.GetState()) } if err := c.IssueImplicitIDToken(ctx, ar, resp); err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } // there is no need to check for https, because implicit flow does not require https diff --git a/handler/openid/flow_implicit_test.go b/handler/openid/flow_implicit_test.go index 890dca9b2..4a88d4ae0 100644 --- a/handler/openid/flow_implicit_test.go +++ b/handler/openid/flow_implicit_test.go @@ -204,9 +204,9 @@ func TestImplicit_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectImplicitHandler(fosite.MinParameterEntropy) }, check: func() { - assert.NotEmpty(t, aresp.GetFragment().Get("id_token")) - assert.NotEmpty(t, aresp.GetFragment().Get("state")) - assert.Empty(t, aresp.GetFragment().Get("access_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("state")) + assert.Empty(t, aresp.GetParameters().Get("access_token")) }, }, { @@ -216,9 +216,9 @@ func TestImplicit_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectImplicitHandler(fosite.MinParameterEntropy) }, check: func() { - assert.NotEmpty(t, aresp.GetFragment().Get("id_token")) - assert.NotEmpty(t, aresp.GetFragment().Get("state")) - assert.NotEmpty(t, aresp.GetFragment().Get("access_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("state")) + assert.NotEmpty(t, aresp.GetParameters().Get("access_token")) }, }, { @@ -229,9 +229,10 @@ func TestImplicit_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectImplicitHandler(fosite.MinParameterEntropy) }, check: func() { - assert.NotEmpty(t, aresp.GetFragment().Get("id_token")) - assert.NotEmpty(t, aresp.GetFragment().Get("state")) - assert.NotEmpty(t, aresp.GetFragment().Get("access_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("state")) + assert.NotEmpty(t, aresp.GetParameters().Get("access_token")) + assert.Equal(t, fosite.ResponseModeFragment, areq.GetResponseMode()) }, }, { @@ -241,9 +242,9 @@ func TestImplicit_HandleAuthorizeEndpointRequest(t *testing.T) { return makeOpenIDConnectImplicitHandler(4) }, check: func() { - assert.NotEmpty(t, aresp.GetFragment().Get("id_token")) - assert.NotEmpty(t, aresp.GetFragment().Get("state")) - assert.NotEmpty(t, aresp.GetFragment().Get("access_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("id_token")) + assert.NotEmpty(t, aresp.GetParameters().Get("state")) + assert.NotEmpty(t, aresp.GetParameters().Get("access_token")) }, }, } { diff --git a/handler/openid/flow_refresh_token.go b/handler/openid/flow_refresh_token.go index 1f9ef0e38..7a2ba0a98 100644 --- a/handler/openid/flow_refresh_token.go +++ b/handler/openid/flow_refresh_token.go @@ -25,6 +25,10 @@ import ( "context" "time" + "github.com/pborman/uuid" + + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -36,22 +40,22 @@ type OpenIDConnectRefreshHandler struct { func (c *OpenIDConnectRefreshHandler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error { if !request.GetGrantTypes().ExactOne("refresh_token") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !request.GetGrantedScopes().Has("openid") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !request.GetClient().GetGrantTypes().Has("refresh_token") { - return errors.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"refresh_token\".")) + return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"refresh_token\".")) } // Refresh tokens can only be issued by an authorize_code which in turn disables the need to check if the id_token // response type is enabled by the client. // // if !request.GetClient().GetResponseTypes().Has("id_token") { - // return errors.WithStack(fosite.ErrUnknownRequest.WithDebug("The client is not allowed to use response type id_token")) + // return errorsx.WithStack(fosite.ErrUnknownRequest.WithDebug("The client is not allowed to use response type id_token")) // } sess, ok := request.GetSession().(Session) @@ -59,43 +63,51 @@ func (c *OpenIDConnectRefreshHandler) HandleTokenEndpointRequest(ctx context.Con return errors.New("Failed to generate id token because session must be of type fosite/handler/openid.Session") } - // We need to reset the expires at value + // We need to reset the expires at value as this would be the previous expiry. sess.IDTokenClaims().ExpiresAt = time.Time{} - sess.IDTokenClaims().Nonce = "" + + // These will be recomputed in PopulateTokenEndpointResponse + sess.IDTokenClaims().JTI = "" + sess.IDTokenClaims().AccessTokenHash = "" + + // We are not issuing a code so there is no need for this field. + sess.IDTokenClaims().CodeHash = "" + return nil } func (c *OpenIDConnectRefreshHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { if !requester.GetGrantTypes().ExactOne("refresh_token") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !requester.GetGrantedScopes().Has("openid") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } if !requester.GetClient().GetGrantTypes().Has("refresh_token") { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"refresh_token\".")) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client is not allowed to use the authorization grant \"refresh_token\".")) } // Disabled because this is already handled at the authorize_request_handler // if !requester.GetClient().GetResponseTypes().Has("id_token") { - // return errors.WithStack(fosite.ErrUnknownRequest.WithDebug("The client is not allowed to use response type id_token")) + // return errorsx.WithStack(fosite.ErrUnknownRequest.WithDebug("The client is not allowed to use response type id_token")) // } sess, ok := requester.GetSession().(Session) if !ok { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) } claims := sess.IDTokenClaims() if claims.Subject == "" { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) } - hash := c.GetAccessTokenHash(ctx, requester, responder) - - claims.AccessTokenHash = hash + claims.AccessTokenHash = c.GetAccessTokenHash(ctx, requester, responder) + claims.JTI = uuid.New() + claims.CodeHash = "" + claims.IssuedAt = time.Now().Truncate(time.Second) return c.IssueExplicitIDToken(ctx, requester, responder) } diff --git a/handler/openid/helper.go b/handler/openid/helper.go index 3fabfbf2e..ef30c0b98 100644 --- a/handler/openid/helper.go +++ b/handler/openid/helper.go @@ -45,9 +45,8 @@ func (i *IDTokenHandleHelper) GetAccessTokenHash(ctx context.Context, requester panic(err) } hashBuf := bytes.NewBuffer(hash.Sum([]byte{})) - len := hashBuf.Len() - return base64.RawURLEncoding.EncodeToString(hashBuf.Bytes()[:len/2]) + return base64.RawURLEncoding.EncodeToString(hashBuf.Bytes()[:hashBuf.Len()/2]) } func (i *IDTokenHandleHelper) generateIDToken(ctx context.Context, fosr fosite.Requester) (token string, err error) { @@ -64,8 +63,7 @@ func (i *IDTokenHandleHelper) IssueImplicitIDToken(ctx context.Context, ar fosit if err != nil { return err } - - resp.AddFragment("id_token", token) + resp.AddParameter("id_token", token) return nil } diff --git a/handler/openid/helper_test.go b/handler/openid/helper_test.go index ed2bdb159..035924c62 100644 --- a/handler/openid/helper_test.go +++ b/handler/openid/helper_test.go @@ -117,7 +117,7 @@ func TestIssueImplicitToken(t *testing.T) { Subject: "peter", }, Headers: &jwt.Headers{}}) - resp.EXPECT().AddFragment("id_token", gomock.Any()) + resp.EXPECT().AddParameter("id_token", gomock.Any()) h := &IDTokenHandleHelper{IDTokenStrategy: strat} err := h.IssueImplicitIDToken(nil, ar, resp) assert.NoError(t, err) diff --git a/handler/openid/strategy_jwt.go b/handler/openid/strategy_jwt.go index 850940d08..300708309 100644 --- a/handler/openid/strategy_jwt.go +++ b/handler/openid/strategy_jwt.go @@ -26,6 +26,8 @@ import ( "strconv" "time" + "github.com/ory/x/errorsx" + jwtgo "github.com/dgrijalva/jwt-go" "github.com/mohae/deepcopy" "github.com/pkg/errors" @@ -137,12 +139,12 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R sess, ok := requester.GetSession().(Session) if !ok { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because session must be of type fosite/handler/openid.Session.")) } claims := sess.IDTokenClaims() if claims.Subject == "" { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because subject is an empty string.")) } if requester.GetRequestForm().Get("grant_type") != "refresh_token" { @@ -153,34 +155,36 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R // Adds a bit of wiggle room for timing issues if claims.AuthTime.After(time.Now().UTC().Add(time.Second * 5)) { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future.")) } if maxAge > 0 { if claims.AuthTime.IsZero() { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time claim is required when max_age is set.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time claim is required when max_age is set.")) } else if claims.RequestedAt.IsZero() { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because requested at claim is required when max_age is set.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because requested at claim is required when max_age is set.")) } else if claims.AuthTime.Add(time.Second * time.Duration(maxAge)).Before(claims.RequestedAt) { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time does not satisfy max_age time.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because authentication time does not satisfy max_age time.")) } } prompt := requester.GetRequestForm().Get("prompt") if prompt != "" { if claims.AuthTime.IsZero() { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Unable to determine validity of prompt parameter because auth_time is missing in id token claims.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Unable to determine validity of prompt parameter because auth_time is missing in id token claims.")) } } switch prompt { case "none": - if claims.AuthTime.After(claims.RequestedAt) { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because prompt was set to \"none\" but auth_time happened after the authorization request was registered, indicating that the user was logged in during this request which is not allowed.")) + if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.After(claims.RequestedAt) { + return "", errorsx.WithStack(fosite.ErrServerError. + WithDebugf("Failed to generate id token because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed.", claims.AuthTime, claims.RequestedAt)) } case "login": - if claims.AuthTime.Before(claims.RequestedAt) { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because prompt was set to \"login\" but auth_time happened before the authorization request was registered, indicating that the user was not re-authenticated which is forbidden.")) + if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.Before(claims.RequestedAt) { + return "", errorsx.WithStack(fosite.ErrServerError. + WithDebugf("Failed to generate id token because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden.", claims.AuthTime, claims.RequestedAt)) } } @@ -196,15 +200,15 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R if errors.As(err, &ve) && ve.Errors == jwtgo.ValidationErrorExpired { // Expired ID Tokens are allowed as values to id_token_hint } else if err != nil { - return "", errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebugf("Unable to decode id token from id_token_hint parameter because %s.", err.Error())) + return "", errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebugf("Unable to decode id token from 'id_token_hint' parameter because %s.", err.Error())) } if hintClaims, ok := tokenHint.Claims.(jwtgo.MapClaims); !ok { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Unable to decode id token from id_token_hint to *jwt.StandardClaims.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Unable to decode id token from 'id_token_hint' to *jwt.StandardClaims.")) } else if hintSub, _ := hintClaims["sub"].(string); hintSub == "" { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Provided id token from id_token_hint does not have a subject.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject.")) } else if hintSub != claims.Subject { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from id_token_hint.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'.")) } } } @@ -214,26 +218,26 @@ func (h DefaultStrategy) GenerateIDToken(ctx context.Context, requester fosite.R } if claims.ExpiresAt.Before(time.Now().UTC()) { - return "", errors.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because expiry claim can not be in the past.")) + return "", errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to generate id token because expiry claim can not be in the past.")) } if claims.AuthTime.IsZero() { - claims.AuthTime = time.Now().UTC() + claims.AuthTime = time.Now().Truncate(time.Second).UTC() } if claims.Issuer == "" { claims.Issuer = h.Issuer } - nonce := requester.GetRequestForm().Get("nonce") // OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. - if len(nonce) == 0 { + if nonce := requester.GetRequestForm().Get("nonce"); len(nonce) == 0 { } else if len(nonce) > 0 && len(nonce) < h.MinParameterEntropy { // We're assuming that using less then, by default, 8 characters for the state can not be considered "unguessable" - return "", errors.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter \"nonce\" is set but does not satisfy the minimum entropy of %d characters.", h.MinParameterEntropy)) + return "", errorsx.WithStack(fosite.ErrInsufficientEntropy.WithHintf("Parameter 'nonce' is set but does not satisfy the minimum entropy of %d characters.", h.MinParameterEntropy)) + } else if len(nonce) > 0 { + claims.Nonce = nonce } - claims.Nonce = nonce claims.Audience = stringslice.Unique(append(claims.Audience, requester.GetClient().GetID())) claims.IssuedAt = time.Now().UTC() diff --git a/handler/openid/validator.go b/handler/openid/validator.go index 8531bee16..6a079790b 100644 --- a/handler/openid/validator.go +++ b/handler/openid/validator.go @@ -28,6 +28,8 @@ import ( "strings" "time" + "github.com/ory/x/errorsx" + jwtgo "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" @@ -90,18 +92,18 @@ func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req if stringslice.Has(prompt, "none") { if !v.secureChecker()(req.GetRedirectURI()) { - return errors.WithStack(fosite.ErrConsentRequired.WithHint("OAuth 2.0 Client is marked public and redirect uri is not considered secure (https missing), but \"prompt=none\" was requested.")) + return errorsx.WithStack(fosite.ErrConsentRequired.WithHint("OAuth 2.0 Client is marked public and redirect uri is not considered secure (https missing), but \"prompt=none\" was requested.")) } } } if !isWhitelisted(prompt, v.AllowedPrompt) { - return errors.WithStack(fosite.ErrInvalidRequest.WithHintf(`Used unknown value "%s" for prompt parameter`, prompt)) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("Used unknown value '%s' for prompt parameter", prompt)) } if stringslice.Has(prompt, "none") && len(prompt) > 1 { // If this parameter contains none with any other value, an error is returned. - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed.")) } maxAge, err := strconv.ParseInt(req.GetRequestForm().Get("max_age"), 10, 64) @@ -111,41 +113,42 @@ func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req session, ok := req.GetSession().(Session) if !ok { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session is not of type fosite/handler/openid.Session.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session is not of type fosite/handler/openid.Session.")) } claims := session.IDTokenClaims() if claims.Subject == "" { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session subject is empty.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because session subject is empty.")) } // Adds a bit of wiggle room for timing issues if claims.AuthTime.After(time.Now().UTC().Add(time.Second * 5)) { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time is in the future.")) } if maxAge > 0 { if claims.AuthTime.IsZero() { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time claim is required when max_age is set.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because authentication time claim is required when max_age is set.")) } else if claims.RequestedAt.IsZero() { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because requested at claim is required when max_age is set.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because requested at claim is required when max_age is set.")) } else if claims.AuthTime.Add(time.Second * time.Duration(maxAge)).Before(claims.RequestedAt) { - return errors.WithStack(fosite.ErrLoginRequired.WithDebug("Failed to validate OpenID Connect request because authentication time does not satisfy max_age time.")) + return errorsx.WithStack(fosite.ErrLoginRequired.WithDebug("Failed to validate OpenID Connect request because authentication time does not satisfy max_age time.")) } } if stringslice.Has(prompt, "none") { if claims.AuthTime.IsZero() { - return errors.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because because auth_time is missing from session.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("Failed to validate OpenID Connect request because because auth_time is missing from session.")) } - if claims.AuthTime.After(claims.RequestedAt) { - return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to \"none\" but auth_time happened after the authorization request was registered, indicating that the user was logged in during this request which is not allowed.")) + if !claims.AuthTime.Equal(claims.RequestedAt) && claims.AuthTime.After(claims.RequestedAt) { + // !claims.AuthTime.Truncate(time.Second).Equal(claims.RequestedAt) && claims.AuthTime.Truncate(time.Second).Before(claims.RequestedAt) { + return errorsx.WithStack(fosite.ErrLoginRequired.WithHintf("Failed to validate OpenID Connect request because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed.", claims.AuthTime, claims.RequestedAt)) } } if stringslice.Has(prompt, "login") { if claims.AuthTime.Before(claims.RequestedAt) { - return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because prompt was set to \"login\" but auth_time happened before the authorization request was registered, indicating that the user was not re-authenticated which is forbidden.")) + return errorsx.WithStack(fosite.ErrLoginRequired.WithHintf("Failed to validate OpenID Connect request because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden.", claims.AuthTime, claims.RequestedAt)) } } @@ -159,15 +162,15 @@ func (v *OpenIDConnectRequestValidator) ValidatePrompt(ctx context.Context, req if errors.As(err, &ve) && ve.Errors == jwtgo.ValidationErrorExpired { // Expired tokens are ok } else if err != nil { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request as decoding id token from id_token_hint parameter failed.").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request as decoding id token from id_token_hint parameter failed.").WithWrap(err).WithDebug(err.Error())) } if hintClaims, ok := tokenHint.Claims.(jwtgo.MapClaims); !ok { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request as decoding id token from id_token_hint to jwtgo.MapClaims failed.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request as decoding id token from id_token_hint to jwtgo.MapClaims failed.")) } else if hintSub, _ := hintClaims["sub"].(string); hintSub == "" { - return errors.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request because provided id token from id_token_hint does not have a subject.")) + return errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Failed to validate OpenID Connect request because provided id token from id_token_hint does not have a subject.")) } else if hintSub != claims.Subject { - return errors.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because the subject from provided id token from id_token_hint does not match the current session's subject.")) + return errorsx.WithStack(fosite.ErrLoginRequired.WithHint("Failed to validate OpenID Connect request because the subject from provided id token from id_token_hint does not match the current session's subject.")) } return nil diff --git a/handler/pkce/handler.go b/handler/pkce/handler.go index cd97ebd00..489491339 100644 --- a/handler/pkce/handler.go +++ b/handler/pkce/handler.go @@ -27,6 +27,8 @@ import ( "encoding/base64" "regexp" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -65,7 +67,7 @@ func (c *Handler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite. code := resp.GetCode() if len(code) == 0 { - return errors.WithStack(fosite.ErrServerError.WithDebug("The PKCE handler must be loaded after the authorize code handler.")) + return errorsx.WithStack(fosite.ErrServerError.WithDebug("The PKCE handler must be loaded after the authorize code handler.")) } signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(code) @@ -73,7 +75,7 @@ func (c *Handler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite. "code_challenge", "code_challenge_method", })); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } return nil @@ -88,12 +90,12 @@ func (c *Handler) validate(challenge, method string, client fosite.Client) error // "error_description" or the response of "error_uri" SHOULD explain the // nature of error, e.g., code challenge required. if c.Force { - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing."). WithDebug("The server is configured in a way that enforces PKCE for clients.")) } if c.ForceForPublicClients && client.IsPublic() { - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithHint("This client must include a code_challenge when performing the authorize code flow, but it is missing."). WithDebug("The server is configured in a way that enforces PKCE for this client.")) } @@ -113,12 +115,12 @@ func (c *Handler) validate(challenge, method string, client fosite.Client) error fallthrough case "": if !c.EnablePlainChallengeMethod { - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithHint("Clients must use code_challenge_method=S256, plain is not allowed."). WithDebug("The server is configured in a way that enforces PKCE S256 as challenge method for clients.")) } default: - return errors.WithStack(fosite.ErrInvalidRequest. + return errorsx.WithStack(fosite.ErrInvalidRequest. WithHint("The code_challenge_method is not supported, use S256 instead.")) } return nil @@ -126,7 +128,7 @@ func (c *Handler) validate(challenge, method string, client fosite.Client) error func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error { if !request.GetGrantTypes().ExactOne("authorization_code") { - return errors.WithStack(fosite.ErrUnknownRequest) + return errorsx.WithStack(fosite.ErrUnknownRequest) } // code_verifier @@ -141,13 +143,13 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(code) authorizeRequest, err := c.Storage.GetPKCERequestSession(ctx, signature, request.GetSession()) if errors.Is(err, fosite.ErrNotFound) { - return errors.WithStack(fosite.ErrInvalidGrant.WithHint("Unable to find initial PKCE data tied to this request").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("Unable to find initial PKCE data tied to this request").WithWrap(err).WithDebug(err.Error())) } else if err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } if err := c.Storage.DeletePKCERequestSession(ctx, signature); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } challenge := authorizeRequest.GetRequestForm().Get("code_challenge") @@ -169,14 +171,14 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite // Validation if len(verifier) < 43 { - return errors.WithStack(fosite.ErrInvalidGrant. + return errorsx.WithStack(fosite.ErrInvalidGrant. WithHint("The PKCE code verifier must be at least 43 characters.")) } else if len(verifier) > 128 { - return errors.WithStack(fosite.ErrInvalidGrant. + return errorsx.WithStack(fosite.ErrInvalidGrant. WithHint("The PKCE code verifier can not be longer than 128 characters.")) } else if verifierWrongFormat.MatchString(verifier) { - return errors.WithStack(fosite.ErrInvalidGrant. - WithHint(`The PKCE code verifier must only contain [a-Z] / [0-9] / "-" / "." / "_" / "~"`)) + return errorsx.WithStack(fosite.ErrInvalidGrant. + WithHint("The PKCE code verifier must only contain [a-Z], [0-9], '-', '.', '_', '~'.")) } // Upon receipt of the request at the token endpoint, the server @@ -204,11 +206,11 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite case "S256": hash := sha256.New() if _, err := hash.Write([]byte(verifier)); err != nil { - return errors.WithStack(fosite.ErrServerError.WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error())) } if base64.RawURLEncoding.EncodeToString(hash.Sum([]byte{})) != challenge { - return errors.WithStack(fosite.ErrInvalidGrant. + return errorsx.WithStack(fosite.ErrInvalidGrant. WithHint("The PKCE code challenge did not match the code verifier.")) } break @@ -216,7 +218,7 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite fallthrough default: if verifier != challenge { - return errors.WithStack(fosite.ErrInvalidGrant. + return errorsx.WithStack(fosite.ErrInvalidGrant. WithHint("The PKCE code challenge did not match the code verifier.")) } } diff --git a/handler/pkce/handler_test.go b/handler/pkce/handler_test.go index 27d86388a..2bc7c0656 100644 --- a/handler/pkce/handler_test.go +++ b/handler/pkce/handler_test.go @@ -62,7 +62,7 @@ func TestPKCEHandleAuthorizeEndpointRequest(t *testing.T) { c := &fosite.DefaultClient{} r.Client = c - w.AddQuery("code", "foo") + w.AddParameter("code", "foo") r.Form.Add("code_challenge", "challenge") r.Form.Add("code_challenge_method", "plain") diff --git a/hash_bcrypt.go b/hash_bcrypt.go index 676ae343a..f24f815be 100644 --- a/hash_bcrypt.go +++ b/hash_bcrypt.go @@ -24,7 +24,8 @@ package fosite import ( "context" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" + "golang.org/x/crypto/bcrypt" ) @@ -41,14 +42,14 @@ func (b *BCrypt) Hash(ctx context.Context, data []byte) ([]byte, error) { } s, err := bcrypt.GenerateFromPassword(data, b.WorkFactor) if err != nil { - return nil, errors.WithStack(err) + return nil, errorsx.WithStack(err) } return s, nil } func (b *BCrypt) Compare(ctx context.Context, hash, data []byte) error { if err := bcrypt.CompareHashAndPassword(hash, data); err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } return nil } diff --git a/integration/authorize_form_post_test.go b/integration/authorize_form_post_test.go new file mode 100644 index 000000000..ca1162051 --- /dev/null +++ b/integration/authorize_form_post_test.go @@ -0,0 +1,184 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + * + */ + +package integration_test + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/internal" + "github.com/ory/fosite/token/jwt" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + goauth "golang.org/x/oauth2" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" +) + +func TestAuthorizeFormPostResponseMode(t *testing.T) { + session := &defaultSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: "peter", + }, + Headers: &jwt.Headers{}, + }, + } + f := compose.ComposeAllEnabled(new(compose.Config), fositeStore, []byte("some-secret-thats-random-some-secret-thats-random-"), internal.MustRSAKey()) + ts := mockServer(t, f, session) + defer ts.Close() + + oauthClient := newOAuth2Client(ts) + defaultClient := fositeStore.Clients["my-client"].(*fosite.DefaultClient) + defaultClient.RedirectURIs[0] = ts.URL + "/callback" + responseModeClient := &fosite.DefaultResponseModeClient{ + DefaultClient: defaultClient, + ResponseModes: []fosite.ResponseModeType{fosite.ResponseModeFormPost}, + } + fositeStore.Clients["response-mode-client"] = responseModeClient + oauthClient.ClientID = "response-mode-client" + + var state string + for k, c := range []struct { + description string + setup func() + check func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) + responseType string + }{ + { + description: "implicit grant #1 test with form_post", + responseType: "id_token%20token", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, token.TokenType) + assert.NotEmpty(t, token.AccessToken) + assert.NotEmpty(t, token.Expiry) + assert.NotEmpty(t, iDToken) + }, + }, + { + description: "implicit grant #2 test with form_post", + responseType: "id_token", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, iDToken) + }, + }, + { + description: "Authorization code grant test with form_post", + responseType: "code", + setup: func() { + state = "12345678901234567890" + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + }, + }, + { + description: "Hybrid #1 grant test with form_post", + responseType: "token%20code", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + assert.NotEmpty(t, token.TokenType) + assert.NotEmpty(t, token.AccessToken) + assert.NotEmpty(t, token.Expiry) + }, + }, + { + description: "Hybrid #2 grant test with form_post", + responseType: "token%20id_token%20code", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + assert.NotEmpty(t, iDToken) + assert.NotEmpty(t, token.TokenType) + assert.NotEmpty(t, token.AccessToken) + assert.NotEmpty(t, token.Expiry) + }, + }, + { + description: "Hybrid #3 grant test with form_post", + responseType: "id_token%20code", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + assert.NotEmpty(t, iDToken) + }, + }, + { + description: "error message test for form_post response", + responseType: "foo", + setup: func() { + state = "12345678901234567890" + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, err["ErrorField"]) + assert.NotEmpty(t, err["DescriptionField"]) + }, + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, c.description), func(t *testing.T) { + c.setup() + authURL := strings.Replace(oauthClient.AuthCodeURL(state, goauth.SetAuthURLParam("response_mode", "form_post"), goauth.SetAuthURLParam("nonce", "111111111")), "response_type=code", "response_type="+c.responseType, -1) + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return errors.New("Dont follow redirects") + }, + } + resp, err := client.Get(authURL) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + code, state, token, iDToken, _, errResp, err := internal.ParseFormPostResponse(fositeStore.Clients["response-mode-client"].GetRedirectURIs()[0], resp.Body) + require.NoError(t, err) + c.check(t, state, code, iDToken, token, errResp) + }) + } +} diff --git a/integration/authorize_response_mode_test.go b/integration/authorize_response_mode_test.go new file mode 100644 index 000000000..54eee4bb7 --- /dev/null +++ b/integration/authorize_response_mode_test.go @@ -0,0 +1,295 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + * + */ + +package integration_test + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/pkg/errors" + + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/internal" + "github.com/ory/fosite/token/jwt" + + "github.com/stretchr/testify/require" + goauth "golang.org/x/oauth2" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" +) + +func TestAuthorizeResponseModes(t *testing.T) { + session := &defaultSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: "peter", + }, + Headers: &jwt.Headers{}, + }, + } + f := compose.ComposeAllEnabled(&compose.Config{ + UseLegacyErrorFormat: true, + }, fositeStore, []byte("some-secret-thats-random-some-secret-thats-random-"), internal.MustRSAKey()) + ts := mockServer(t, f, session) + defer ts.Close() + + oauthClient := newOAuth2Client(ts) + defaultClient := fositeStore.Clients["my-client"].(*fosite.DefaultClient) + defaultClient.RedirectURIs[0] = ts.URL + "/callback" + responseModeClient := &fosite.DefaultResponseModeClient{ + DefaultClient: defaultClient, + ResponseModes: []fosite.ResponseModeType{}, + } + fositeStore.Clients["response-mode-client"] = responseModeClient + oauthClient.ClientID = "response-mode-client" + + var state string + for k, c := range []struct { + description string + setup func() + check func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) + responseType string + responseMode string + }{ + { + description: "Should give err because implicit grant with response mode query", + responseType: "id_token%20token", + responseMode: "query", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeQuery} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.NotEmpty(t, err["ErrorField"]) + assert.NotEmpty(t, err["DescriptionField"]) + assert.Equal(t, "Insecure response_mode 'query' for the response_type '[id_token token]'.", err["HintField"]) + }, + }, + { + description: "Should pass implicit grant with response mode form_post", + responseType: "id_token%20token", + responseMode: "form_post", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeFormPost} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, token.TokenType) + assert.NotEmpty(t, token.AccessToken) + assert.NotEmpty(t, token.Expiry) + assert.NotEmpty(t, iDToken) + }, + }, + { + description: "Should fail because response mode form_post is not allowed by the client", + responseType: "id_token%20token", + responseMode: "form_post", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeQuery} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.NotEmpty(t, err["ErrorField"]) + assert.NotEmpty(t, err["DescriptionField"]) + assert.Equal(t, "The client is not allowed to request response_mode 'form_post'.", err["HintField"]) + }, + }, + { + description: "Should fail because response mode form_post is not allowed by the client without legacy format", + responseType: "id_token%20token", + responseMode: "form_post", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeQuery} + f.(*fosite.Fosite).UseLegacyErrorFormat = false + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + f.(*fosite.Fosite).UseLegacyErrorFormat = true // reset + assert.NotEmpty(t, err["ErrorField"]) + assert.Contains(t, err["DescriptionField"], "The client is not allowed to request response_mode 'form_post'.") + assert.Empty(t, err["HintField"]) + }, + }, + { + description: "Should pass Authorization code grant test with response mode fragment", + responseType: "code", + responseMode: "fragment", + setup: func() { + state = "12345678901234567890" + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeFragment} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + }, + }, + { + description: "Should pass Authorization code grant test with response mode form_post", + responseType: "code", + responseMode: "form_post", + setup: func() { + state = "12345678901234567890" + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeFormPost} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + }, + }, + { + description: "Should fail Hybrid grant test with query", + responseType: "token%20code", + responseMode: "query", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeQuery} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + //assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, err["ErrorField"]) + assert.NotEmpty(t, err["DescriptionField"]) + assert.Equal(t, "Insecure response_mode 'query' for the response_type '[token code]'.", err["HintField"]) + }, + }, + { + description: "Should fail Hybrid grant test with query without legacy fields", + responseType: "token%20code", + responseMode: "query", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeQuery} + f.(*fosite.Fosite).UseLegacyErrorFormat = false + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + f.(*fosite.Fosite).UseLegacyErrorFormat = true // reset + + //assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, err["ErrorField"]) + assert.Contains(t, err["DescriptionField"], "Insecure response_mode 'query' for the response_type '[token code]'.") + assert.Empty(t, err["HintField"]) + assert.Empty(t, err["DebugField"]) + }, + }, + { + description: "Should pass Hybrid grant test with form_post", + responseType: "token%20code", + responseMode: "form_post", + setup: func() { + state = "12345678901234567890" + oauthClient.Scopes = []string{"openid"} + responseModeClient.ResponseModes = []fosite.ResponseModeType{fosite.ResponseModeFormPost} + }, + check: func(t *testing.T, stateFromServer string, code string, token goauth.Token, iDToken string, err map[string]string) { + assert.EqualValues(t, state, stateFromServer) + assert.NotEmpty(t, code) + assert.NotEmpty(t, token.TokenType) + assert.NotEmpty(t, token.AccessToken) + assert.NotEmpty(t, token.Expiry) + }, + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, c.description), func(t *testing.T) { + c.setup() + authURL := strings.Replace(oauthClient.AuthCodeURL(state, goauth.SetAuthURLParam("response_mode", c.responseMode), goauth.SetAuthURLParam("nonce", "111111111")), "response_type=code", "response_type="+c.responseType, -1) + + var ( + callbackURL *url.URL + redirErr = errors.New("Dont follow redirects") + ) + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + callbackURL = req.URL + return redirErr + }, + } + + var ( + code, state, iDToken string + token goauth.Token + errResp map[string]string + ) + + resp, err := client.Get(authURL) + if fosite.ResponseModeType(c.responseMode) == fosite.ResponseModeFragment { + // fragment + require.EqualError(t, errors.Unwrap(err), redirErr.Error()) + fragment, err := url.ParseQuery(callbackURL.Fragment) + require.NoError(t, err) + code, state, iDToken, token, errResp = getParameters(t, fragment) + } else if fosite.ResponseModeType(c.responseMode) == fosite.ResponseModeQuery { + // query + require.EqualError(t, errors.Unwrap(err), redirErr.Error()) + query, err := url.ParseQuery(callbackURL.RawQuery) + require.NoError(t, err) + code, state, iDToken, token, errResp = getParameters(t, query) + } else if fosite.ResponseModeType(c.responseMode) == fosite.ResponseModeFormPost { + // form_post + require.NoError(t, err) + code, state, iDToken, token, _, errResp, err = internal.ParseFormPostResponse(fositeStore.Clients["response-mode-client"].GetRedirectURIs()[0], resp.Body) + } else { + t.FailNow() + } + + c.check(t, state, code, token, iDToken, errResp) + }) + } +} + +func getParameters(t *testing.T, param url.Values) (code, state, iDToken string, token goauth.Token, errResp map[string]string) { + errResp = make(map[string]string) + if param.Get("error") != "" { + errResp["ErrorField"] = param.Get("error") + errResp["DescriptionField"] = param.Get("error_description") + errResp["HintField"] = param.Get("error_hint") + } else { + code = param.Get("code") + state = param.Get("state") + iDToken = param.Get("id_token") + token = goauth.Token{ + AccessToken: param.Get("access_token"), + TokenType: param.Get("token_type"), + RefreshToken: param.Get("refresh_token"), + } + if param.Get("expires_in") != "" { + expires, err := strconv.Atoi(param.Get("expires_in")) + require.NoError(t, err) + token.Expiry = time.Now().UTC().Add(time.Duration(expires) * time.Second) + } + } + return +} diff --git a/integration/helper_endpoints_test.go b/integration/helper_endpoints_test.go index 67b4e84c2..860e3f7ff 100644 --- a/integration/helper_endpoints_test.go +++ b/integration/helper_endpoints_test.go @@ -71,7 +71,7 @@ func tokenInfoHandler(t *testing.T, oauth2 fosite.OAuth2Provider, session fosite t.Logf("Info request failed because: %+v", err) var e *fosite.RFC6749Error require.True(t, errors.As(err, &e)) - http.Error(rw, e.Description, e.Code) + http.Error(rw, e.DescriptionField, e.CodeField) return } diff --git a/internal/authorize_request.go b/internal/authorize_request.go index ee88913a6..91717d790 100644 --- a/internal/authorize_request.go +++ b/internal/authorize_request.go @@ -77,6 +77,20 @@ func (mr *MockAuthorizeRequesterMockRecorder) GetClient() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockAuthorizeRequester)(nil).GetClient)) } +// GetDefaultResponseMode mocks base method +func (m *MockAuthorizeRequester) GetDefaultResponseMode() fosite.ResponseModeType { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDefaultResponseMode") + ret0, _ := ret[0].(fosite.ResponseModeType) + return ret0 +} + +// GetDefaultResponseMode indicates an expected call of GetDefaultResponseMode +func (mr *MockAuthorizeRequesterMockRecorder) GetDefaultResponseMode() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultResponseMode", reflect.TypeOf((*MockAuthorizeRequester)(nil).GetDefaultResponseMode)) +} + // GetGrantedAudience mocks base method func (m *MockAuthorizeRequester) GetGrantedAudience() fosite.Arguments { m.ctrl.T.Helper() @@ -189,6 +203,20 @@ func (mr *MockAuthorizeRequesterMockRecorder) GetRequestedScopes() *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestedScopes", reflect.TypeOf((*MockAuthorizeRequester)(nil).GetRequestedScopes)) } +// GetResponseMode mocks base method +func (m *MockAuthorizeRequester) GetResponseMode() fosite.ResponseModeType { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResponseModes") + ret0, _ := ret[0].(fosite.ResponseModeType) + return ret0 +} + +// GetResponseMode indicates an expected call of GetResponseMode +func (mr *MockAuthorizeRequesterMockRecorder) GetResponseMode() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponseModes", reflect.TypeOf((*MockAuthorizeRequester)(nil).GetResponseMode)) +} + // GetResponseTypes mocks base method func (m *MockAuthorizeRequester) GetResponseTypes() fosite.Arguments { m.ctrl.T.Helper() @@ -295,6 +323,18 @@ func (mr *MockAuthorizeRequesterMockRecorder) Sanitize(arg0 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sanitize", reflect.TypeOf((*MockAuthorizeRequester)(nil).Sanitize), arg0) } +// SetDefaultResponseMode mocks base method +func (m *MockAuthorizeRequester) SetDefaultResponseMode(arg0 fosite.ResponseModeType) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetDefaultResponseMode", arg0) +} + +// SetDefaultResponseMode indicates an expected call of SetDefaultResponseMode +func (mr *MockAuthorizeRequesterMockRecorder) SetDefaultResponseMode(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDefaultResponseMode", reflect.TypeOf((*MockAuthorizeRequester)(nil).SetDefaultResponseMode), arg0) +} + // SetID mocks base method func (m *MockAuthorizeRequester) SetID(arg0 string) { m.ctrl.T.Helper() @@ -331,6 +371,18 @@ func (mr *MockAuthorizeRequesterMockRecorder) SetRequestedScopes(arg0 interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRequestedScopes", reflect.TypeOf((*MockAuthorizeRequester)(nil).SetRequestedScopes), arg0) } +// SetResponseMode mocks base method +func (m *MockAuthorizeRequester) SetResponseMode(arg0 fosite.ResponseModeType) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetResponseMode", arg0) +} + +// SetResponseMode indicates an expected call of SetResponseMode +func (mr *MockAuthorizeRequesterMockRecorder) SetResponseMode(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetResponseMode", reflect.TypeOf((*MockAuthorizeRequester)(nil).SetResponseMode), arg0) +} + // SetResponseTypeHandled mocks base method func (m *MockAuthorizeRequester) SetResponseTypeHandled(arg0 string) { m.ctrl.T.Helper() diff --git a/internal/authorize_response.go b/internal/authorize_response.go index 6cde26f24..09bfe76ce 100644 --- a/internal/authorize_response.go +++ b/internal/authorize_response.go @@ -35,18 +35,6 @@ func (m *MockAuthorizeResponder) EXPECT() *MockAuthorizeResponderMockRecorder { return m.recorder } -// AddFragment mocks base method -func (m *MockAuthorizeResponder) AddFragment(arg0, arg1 string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "AddFragment", arg0, arg1) -} - -// AddFragment indicates an expected call of AddFragment -func (mr *MockAuthorizeResponderMockRecorder) AddFragment(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFragment", reflect.TypeOf((*MockAuthorizeResponder)(nil).AddFragment), arg0, arg1) -} - // AddHeader mocks base method func (m *MockAuthorizeResponder) AddHeader(arg0, arg1 string) { m.ctrl.T.Helper() @@ -59,16 +47,16 @@ func (mr *MockAuthorizeResponderMockRecorder) AddHeader(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHeader", reflect.TypeOf((*MockAuthorizeResponder)(nil).AddHeader), arg0, arg1) } -// AddQuery mocks base method -func (m *MockAuthorizeResponder) AddQuery(arg0, arg1 string) { +// AddParameter mocks base method +func (m *MockAuthorizeResponder) AddParameter(arg0, arg1 string) { m.ctrl.T.Helper() - m.ctrl.Call(m, "AddQuery", arg0, arg1) + m.ctrl.Call(m, "AddParameter", arg0, arg1) } -// AddQuery indicates an expected call of AddQuery -func (mr *MockAuthorizeResponderMockRecorder) AddQuery(arg0, arg1 interface{}) *gomock.Call { +// AddParameter indicates an expected call of AddParameter +func (mr *MockAuthorizeResponderMockRecorder) AddParameter(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddQuery", reflect.TypeOf((*MockAuthorizeResponder)(nil).AddQuery), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddParameter", reflect.TypeOf((*MockAuthorizeResponder)(nil).AddParameter), arg0, arg1) } // GetCode mocks base method @@ -85,20 +73,6 @@ func (mr *MockAuthorizeResponderMockRecorder) GetCode() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCode", reflect.TypeOf((*MockAuthorizeResponder)(nil).GetCode)) } -// GetFragment mocks base method -func (m *MockAuthorizeResponder) GetFragment() url.Values { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFragment") - ret0, _ := ret[0].(url.Values) - return ret0 -} - -// GetFragment indicates an expected call of GetFragment -func (mr *MockAuthorizeResponderMockRecorder) GetFragment() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFragment", reflect.TypeOf((*MockAuthorizeResponder)(nil).GetFragment)) -} - // GetHeader mocks base method func (m *MockAuthorizeResponder) GetHeader() http.Header { m.ctrl.T.Helper() @@ -113,16 +87,16 @@ func (mr *MockAuthorizeResponderMockRecorder) GetHeader() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeader", reflect.TypeOf((*MockAuthorizeResponder)(nil).GetHeader)) } -// GetQuery mocks base method -func (m *MockAuthorizeResponder) GetQuery() url.Values { +// GetParameters mocks base method +func (m *MockAuthorizeResponder) GetParameters() url.Values { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetQuery") + ret := m.ctrl.Call(m, "GetParameters") ret0, _ := ret[0].(url.Values) return ret0 } -// GetQuery indicates an expected call of GetQuery -func (mr *MockAuthorizeResponderMockRecorder) GetQuery() *gomock.Call { +// GetParameters indicates an expected call of GetParameters +func (mr *MockAuthorizeResponderMockRecorder) GetParameters() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQuery", reflect.TypeOf((*MockAuthorizeResponder)(nil).GetQuery)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParameters", reflect.TypeOf((*MockAuthorizeResponder)(nil).GetParameters)) } diff --git a/internal/introspector.go b/internal/introspector.go index 138ef9dd3..2a821e0db 100644 --- a/internal/introspector.go +++ b/internal/introspector.go @@ -9,6 +9,7 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" + fosite "github.com/ory/fosite" ) diff --git a/internal/test_helpers.go b/internal/test_helpers.go new file mode 100644 index 000000000..194afe5c4 --- /dev/null +++ b/internal/test_helpers.go @@ -0,0 +1,141 @@ +/* + * Copyright © 2015-2020 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2020 Aeneas Rekkas + * @license Apache-2.0 + * + */ + +package internal + +import ( + "errors" + "net/url" + + "io" + "strconv" + "time" + + "golang.org/x/net/html" + goauth "golang.org/x/oauth2" +) + +func ParseFormPostResponse(redirectURL string, resp io.ReadCloser) (authorizationCode, stateFromServer, iDToken string, token goauth.Token, customParameters url.Values, rFC6749Error map[string]string, err error) { + token = goauth.Token{} + rFC6749Error = map[string]string{} + customParameters = url.Values{} + + doc, err := html.Parse(resp) + if err != nil { + return "", "", "", token, customParameters, rFC6749Error, err + } + + //doc>html>body + body := findBody(doc.FirstChild.FirstChild) + if body.Data != "body" { + return "", "", "", token, customParameters, rFC6749Error, errors.New("Malformed html") + } + + htmlEvent := body.Attr[0].Key + if htmlEvent != "onload" { + return "", "", "", token, customParameters, rFC6749Error, errors.New("onload event is missing") + } + + onLoadFunc := body.Attr[0].Val + if onLoadFunc != "javascript:document.forms[0].submit()" { + return "", "", "", token, customParameters, rFC6749Error, errors.New("onload function is missing") + } + + form := getNextNoneTextNode(body.FirstChild) + if form.Data != "form" { + return "", "", "", token, customParameters, rFC6749Error, errors.New("html form is missing") + } + + for _, attr := range form.Attr { + if attr.Key == "method" { + if attr.Val != "post" { + return "", "", "", token, customParameters, rFC6749Error, errors.New("html form post method is missing") + } + } else { + if attr.Val != redirectURL { + return "", "", "", token, customParameters, rFC6749Error, errors.New("html form post url is wrong") + } + } + } + + for node := getNextNoneTextNode(form.FirstChild); node != nil; node = getNextNoneTextNode(node.NextSibling) { + var k, v string + for _, attr := range node.Attr { + if attr.Key == "name" { + k = attr.Val + } else if attr.Key == "value" { + v = attr.Val + } + + } + + switch k { + case "state": + stateFromServer = v + case "code": + authorizationCode = v + case "expires_in": + expires, err := strconv.Atoi(v) + if err != nil { + return "", "", "", token, customParameters, rFC6749Error, err + } + token.Expiry = time.Now().UTC().Add(time.Duration(expires) * time.Second) + case "access_token": + token.AccessToken = v + case "token_type": + token.TokenType = v + case "refresh_token": + token.RefreshToken = v + case "error": + rFC6749Error["ErrorField"] = v + case "error_hint": + rFC6749Error["HintField"] = v + case "error_description": + rFC6749Error["DescriptionField"] = v + case "id_token": + iDToken = v + default: + customParameters.Add(k, v) + } + } + + return +} + +func getNextNoneTextNode(node *html.Node) *html.Node { + nextNode := node.NextSibling + if nextNode != nil && nextNode.Type == html.TextNode { + nextNode = getNextNoneTextNode(node.NextSibling) + } + + return nextNode +} + +func findBody(node *html.Node) *html.Node { + if node != nil { + if node.Data == "body" { + return node + } + return findBody(node.NextSibling) + } + + return nil +} diff --git a/introspect.go b/introspect.go index 875c9c985..344c775e2 100644 --- a/introspect.go +++ b/introspect.go @@ -26,6 +26,8 @@ import ( "net/http" "strings" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" ) @@ -66,12 +68,12 @@ func (f *Fosite) IntrospectToken(ctx context.Context, token string, tokenUse Tok // do nothing } else { rfcerr := ErrorToRFC6749Error(err) - return "", nil, errors.WithStack(rfcerr) + return "", nil, errorsx.WithStack(rfcerr) } } if !found { - return "", nil, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to find a suitable validation strategy for the token, thus it is invalid.")) + return "", nil, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to find a suitable validation strategy for the token, thus it is invalid.")) } return foundTokenUse, ar, nil diff --git a/introspection_request_handler.go b/introspection_request_handler.go index 9b5fc6750..f2f1b28b0 100644 --- a/introspection_request_handler.go +++ b/introspection_request_handler.go @@ -27,7 +27,7 @@ import ( "net/url" "strings" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" ) // NewIntrospectionRequest initiates token introspection as defined in @@ -111,11 +111,11 @@ import ( // token=mF_9.B5f-4.1JqM&token_type_hint=access_token func (f *Fosite) NewIntrospectionRequest(ctx context.Context, r *http.Request, session Session) (IntrospectionResponder, error) { if r.Method != "POST" { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s' but expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) } token := r.PostForm.Get("token") @@ -123,44 +123,44 @@ func (f *Fosite) NewIntrospectionRequest(ctx context.Context, r *http.Request, s scope := r.PostForm.Get("scope") if clientToken := AccessTokenFromRequest(r); clientToken != "" { if token == clientToken { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("Bearer and introspection token are identical.")) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Bearer and introspection token are identical.")) } if tu, _, err := f.IntrospectToken(ctx, clientToken, AccessToken, session.Clone()); err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing, malformed, or credentials used are invalid.")) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing, malformed, or credentials used are invalid.")) } else if tu != "" && tu != AccessToken { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHintf("HTTP Authorization header did not provide a token of type \"access_token\", got type \"%s\".", tu)) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHintf("HTTP Authorization header did not provide a token of type 'access_token', got type '%s'.", tu)) } } else { id, secret, ok := r.BasicAuth() if !ok { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing.")) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing.")) } clientID, err := url.QueryUnescape(id) if err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client ID from HTTP basic authorization header, make sure it is properly encoded.").WithCause(err).WithDebug(err.Error())) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client ID from HTTP basic authorization header, make sure it is properly encoded.").WithWrap(err).WithDebug(err.Error())) } clientSecret, err := url.QueryUnescape(secret) if err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client Secret from HTTP basic authorization header, make sure it is properly encoded.").WithCause(err).WithDebug(err.Error())) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client Secret from HTTP basic authorization header, make sure it is properly encoded.").WithWrap(err).WithDebug(err.Error())) } client, err := f.Store.GetClient(ctx, clientID) if err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("Unable to find OAuth 2.0 Client from HTTP basic authorization header.").WithCause(err).WithDebug(err.Error())) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to find OAuth 2.0 Client from HTTP basic authorization header.").WithWrap(err).WithDebug(err.Error())) } // Enforce client authentication if err := f.Hasher.Compare(ctx, client.GetHashedSecret(), []byte(clientSecret)); err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrRequestUnauthorized.WithHint("OAuth 2.0 Client credentials are invalid.")) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("OAuth 2.0 Client credentials are invalid.")) } } tu, ar, err := f.IntrospectToken(ctx, token, TokenUse(tokenTypeHint), session, RemoveEmpty(strings.Split(scope, " "))...) if err != nil { - return &IntrospectionResponse{Active: false}, errors.WithStack(ErrInactiveToken.WithHint("An introspection strategy indicated that the token is inactive.").WithCause(err).WithDebug(err.Error())) + return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInactiveToken.WithHint("An introspection strategy indicated that the token is inactive.").WithWrap(err).WithDebug(err.Error())) } accessTokenType := "" diff --git a/introspection_response_writer_test.go b/introspection_response_writer_test.go index b5925dc81..a6315d7c4 100644 --- a/introspection_response_writer_test.go +++ b/introspection_response_writer_test.go @@ -28,6 +28,8 @@ import ( "testing" "time" + "github.com/ory/x/errorsx" + "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -46,17 +48,17 @@ func TestWriteIntrospectionError(t *testing.T) { rw.EXPECT().WriteHeader(http.StatusUnauthorized) rw.EXPECT().Header().AnyTimes().Return(http.Header{}) rw.EXPECT().Write(gomock.Any()) - f.WriteIntrospectionError(rw, errors.WithStack(ErrRequestUnauthorized)) + f.WriteIntrospectionError(rw, errorsx.WithStack(ErrRequestUnauthorized)) rw.EXPECT().WriteHeader(http.StatusBadRequest) rw.EXPECT().Write(gomock.Any()) - f.WriteIntrospectionError(rw, errors.WithStack(ErrInvalidRequest)) + f.WriteIntrospectionError(rw, errorsx.WithStack(ErrInvalidRequest)) rw.EXPECT().Write([]byte("{\"active\":false}\n")) f.WriteIntrospectionError(rw, errors.New("")) rw.EXPECT().Write([]byte("{\"active\":false}\n")) - f.WriteIntrospectionError(rw, errors.WithStack(ErrInactiveToken.WithCause(ErrRequestUnauthorized))) + f.WriteIntrospectionError(rw, errorsx.WithStack(ErrInactiveToken.WithWrap(ErrRequestUnauthorized))) f.WriteIntrospectionError(rw, nil) } diff --git a/oauth2.go b/oauth2.go index 01e42009e..66316d80c 100644 --- a/oauth2.go +++ b/oauth2.go @@ -268,6 +268,15 @@ type AuthorizeRequester interface { // GetState returns the request's state. GetState() (state string) + // GetResponseMode returns response_mode of the authorization request + GetResponseMode() ResponseModeType + + // SetDefaultResponseMode sets default response mode for a response type in a flow + SetDefaultResponseMode(responseMode ResponseModeType) + + // GetDefaultResponseMode gets default response mode for a response type in a flow + GetDefaultResponseMode() ResponseModeType + Requester } @@ -310,15 +319,9 @@ type AuthorizeResponder interface { // AddHeader adds an header key value pair to the response AddHeader(key, value string) - // GetQuery returns the response's query - GetQuery() (query url.Values) - - // AddQuery adds an url query key value pair to the response - AddQuery(key, value string) - - // GetHeader returns the response's url fragments - GetFragment() (fragment url.Values) + // GetParameters returns the response's parameters + GetParameters() (query url.Values) - // AddHeader adds a key value pair to the response's url fragment - AddFragment(key, value string) + // AddParameter adds key value pair to the response + AddParameter(key, value string) } diff --git a/revoke_handler.go b/revoke_handler.go index 4597ca50b..66cab3dda 100644 --- a/revoke_handler.go +++ b/revoke_handler.go @@ -27,6 +27,8 @@ import ( "fmt" "net/http" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" ) @@ -49,11 +51,11 @@ import ( // server and does not influence the revocation response. func (f *Fosite) NewRevocationRequest(ctx context.Context, r *http.Request) error { if r.Method != "POST" { - return errors.WithStack(ErrInvalidRequest.WithHintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) + return errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s' but expected 'POST'.", r.Method)) } else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart { - return errors.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithCause(err).WithDebug(err.Error())) + return errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error())) } else if len(r.PostForm) == 0 { - return errors.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) + return errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty.")) } client, err := f.AuthenticateClient(ctx, r, r.PostForm) @@ -76,7 +78,7 @@ func (f *Fosite) NewRevocationRequest(ctx context.Context, r *http.Request) erro } if !found { - return errors.WithStack(ErrInvalidRequest) + return errorsx.WithStack(ErrInvalidRequest) } return nil @@ -111,7 +113,7 @@ func (f *Fosite) WriteRevocationResponse(rw http.ResponseWriter, err error) { return } - rw.WriteHeader(ErrInvalidRequest.Code) + rw.WriteHeader(ErrInvalidRequest.CodeField) _, _ = rw.Write(js) } else if errors.Is(err, ErrInvalidClient) { rw.Header().Set("Content-Type", "application/json;charset=UTF-8") @@ -122,7 +124,7 @@ func (f *Fosite) WriteRevocationResponse(rw http.ResponseWriter, err error) { return } - rw.WriteHeader(ErrInvalidClient.Code) + rw.WriteHeader(ErrInvalidClient.CodeField) _, _ = rw.Write(js) } else { // 200 OK diff --git a/revoke_handler_test.go b/revoke_handler_test.go index ad2a8bc60..6e76f37f0 100644 --- a/revoke_handler_test.go +++ b/revoke_handler_test.go @@ -240,14 +240,14 @@ func TestWriteRevocationResponse(t *testing.T) { rw: httptest.NewRecorder(), err: ErrInvalidRequest, }, - expectCode: ErrInvalidRequest.Code, + expectCode: ErrInvalidRequest.CodeField, }, { input: args{ rw: httptest.NewRecorder(), err: ErrInvalidClient, }, - expectCode: ErrInvalidClient.Code, + expectCode: ErrInvalidClient.CodeField, }, { input: args{ diff --git a/token/hmac/bytes.go b/token/hmac/bytes.go index 62b2479e8..3d4e66acd 100644 --- a/token/hmac/bytes.go +++ b/token/hmac/bytes.go @@ -25,14 +25,14 @@ import ( "crypto/rand" "io" - "github.com/pkg/errors" + "github.com/ory/x/errorsx" ) // RandomBytes returns n random bytes by reading from crypto/rand.Reader func RandomBytes(n int) ([]byte, error) { bytes := make([]byte, n) if _, err := io.ReadFull(rand.Reader, bytes); err != nil { - return []byte{}, errors.WithStack(err) + return []byte{}, errorsx.WithStack(err) } return bytes, nil } diff --git a/token/hmac/hmacsha.go b/token/hmac/hmacsha.go index 88dd72bea..6ac51d35b 100644 --- a/token/hmac/hmacsha.go +++ b/token/hmac/hmacsha.go @@ -32,6 +32,8 @@ import ( "strings" "sync" + "github.com/ory/x/errorsx" + "github.com/pkg/errors" "github.com/ory/fosite" @@ -81,7 +83,7 @@ func (c *HMACStrategy) Generate() (string, string, error) { // by the authorization server. tokenKey, err := RandomBytes(c.TokenEntropy) if err != nil { - return "", "", errors.WithStack(err) + return "", "", errorsx.WithStack(err) } signature := generateHMAC(tokenKey, &signingKey) @@ -129,29 +131,29 @@ func (c *HMACStrategy) validate(secret []byte, token string) error { split := strings.Split(token, ".") if len(split) != 2 { - return errors.WithStack(fosite.ErrInvalidTokenFormat) + return errorsx.WithStack(fosite.ErrInvalidTokenFormat) } tokenKey := split[0] tokenSignature := split[1] if tokenKey == "" || tokenSignature == "" { - return errors.WithStack(fosite.ErrInvalidTokenFormat) + return errorsx.WithStack(fosite.ErrInvalidTokenFormat) } decodedTokenSignature, err := b64.DecodeString(tokenSignature) if err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } decodedTokenKey, err := b64.DecodeString(tokenKey) if err != nil { - return errors.WithStack(err) + return errorsx.WithStack(err) } expectedMAC := generateHMAC(decodedTokenKey, &signingKey) if !hmac.Equal(expectedMAC, decodedTokenSignature) { // Hash is invalid - return errors.WithStack(fosite.ErrTokenSignatureMismatch) + return errorsx.WithStack(fosite.ErrTokenSignatureMismatch) } return nil diff --git a/token/jwt/jwt.go b/token/jwt/jwt.go index 5ac82b13f..f052b602b 100644 --- a/token/jwt/jwt.go +++ b/token/jwt/jwt.go @@ -32,6 +32,8 @@ import ( "fmt" "strings" + "github.com/ory/x/errorsx" + jwt "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" @@ -64,11 +66,11 @@ func (j *RS256JWTStrategy) Generate(ctx context.Context, claims jwt.Claims, head var sig, sstr string var err error if sstr, err = token.SigningString(); err != nil { - return "", "", errors.WithStack(err) + return "", "", errorsx.WithStack(err) } if sig, err = token.Method.Sign(sstr, j.PrivateKey); err != nil { - return "", "", errors.WithStack(err) + return "", "", errorsx.WithStack(err) } return fmt.Sprintf("%s.%s", sstr, sig), sig, nil @@ -77,7 +79,7 @@ func (j *RS256JWTStrategy) Generate(ctx context.Context, claims jwt.Claims, head // Validate validates a token and returns its signature or an error if the token is not valid. func (j *RS256JWTStrategy) Validate(ctx context.Context, token string) (string, error) { if _, err := j.Decode(ctx, token); err != nil { - return "", errors.WithStack(err) + return "", errorsx.WithStack(err) } return j.GetSignature(ctx, token) @@ -94,9 +96,9 @@ func (j *RS256JWTStrategy) Decode(ctx context.Context, token string) (*jwt.Token }) if err != nil { - return parsedToken, errors.WithStack(err) + return parsedToken, errorsx.WithStack(err) } else if !parsedToken.Valid { - return parsedToken, errors.WithStack(fosite.ErrInactiveToken) + return parsedToken, errorsx.WithStack(fosite.ErrInactiveToken) } return parsedToken, err @@ -117,7 +119,7 @@ func (j *RS256JWTStrategy) Hash(ctx context.Context, in []byte) ([]byte, error) hash := sha256.New() _, err := hash.Write(in) if err != nil { - return []byte{}, errors.WithStack(err) + return []byte{}, errorsx.WithStack(err) } return hash.Sum([]byte{}), nil } @@ -144,11 +146,11 @@ func (j *ES256JWTStrategy) Generate(ctx context.Context, claims jwt.Claims, head var sig, sstr string var err error if sstr, err = token.SigningString(); err != nil { - return "", "", errors.WithStack(err) + return "", "", errorsx.WithStack(err) } if sig, err = token.Method.Sign(sstr, j.PrivateKey); err != nil { - return "", "", errors.WithStack(err) + return "", "", errorsx.WithStack(err) } return fmt.Sprintf("%s.%s", sstr, sig), sig, nil @@ -157,7 +159,7 @@ func (j *ES256JWTStrategy) Generate(ctx context.Context, claims jwt.Claims, head // Validate validates a token and returns its signature or an error if the token is not valid. func (j *ES256JWTStrategy) Validate(ctx context.Context, token string) (string, error) { if _, err := j.Decode(ctx, token); err != nil { - return "", errors.WithStack(err) + return "", errorsx.WithStack(err) } return j.GetSignature(ctx, token) @@ -174,9 +176,9 @@ func (j *ES256JWTStrategy) Decode(ctx context.Context, token string) (*jwt.Token }) if err != nil { - return parsedToken, errors.WithStack(err) + return parsedToken, errorsx.WithStack(err) } else if !parsedToken.Valid { - return parsedToken, errors.WithStack(fosite.ErrInactiveToken) + return parsedToken, errorsx.WithStack(fosite.ErrInactiveToken) } return parsedToken, err @@ -197,7 +199,7 @@ func (j *ES256JWTStrategy) Hash(ctx context.Context, in []byte) ([]byte, error) hash := sha256.New() _, err := hash.Write(in) if err != nil { - return []byte{}, errors.WithStack(err) + return []byte{}, errorsx.WithStack(err) } return hash.Sum([]byte{}), nil }