diff --git a/.gitignore b/.gitignore index 4cf41630265..3293b120f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ node_modules/ .DS_Store vendor/ .hydra.yml -cover.out \ No newline at end of file +cover.out +output/ +_book/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index bb278c30673..2731bfbfb53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ language: go go: - 1.5 - 1.6 + - 1.7 install: - go get github.com/mattn/goveralls golang.org/x/tools/cmd/cover github.com/pierrre/gotestcover github.com/Masterminds/glide diff --git a/README.md b/README.md index 943fd41d42e..7a5175a8e78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![Ory/Hydra](dist/logo.png) +# ![Ory/Hydra](docs/dist/images/logo.png) [![Join the chat at https://gitter.im/ory-am/hydra](https://img.shields.io/badge/join-chat-00cc99.svg)](https://gitter.im/ory-am/hydra?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join mailinglist](https://img.shields.io/badge/join-mailinglist-00cc99.svg)](https://groups.google.com/forum/#!forum/ory-hydra/new) @@ -12,13 +12,15 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/ory-am/hydra)](https://goreportcard.com/report/github.com/ory-am/hydra) -[![Docs Guide](https://img.shields.io/badge/docs-overview-blue.svg)](https://ory-am.gitbooks.io/hydra/content/) +[![Docs Guide](https://img.shields.io/badge/docs-guide-blue.svg)](https://ory-am.gitbooks.io/hydra/content/) [![HTTP API Documentation](https://img.shields.io/badge/docs-http%20api-blue.svg)](http://docs.hdyra.apiary.io/) [![Code Documentation](https://img.shields.io/badge/docs-godoc-blue.svg)](https://godoc.org/github.com/ory-am/hydra) -Hydra is being developed by german-based company [Ory](https://ory.am). Join our [newsletter](http://eepurl.com/bKT3N9) to stay on top of new developments. We respond to *basic support requests in our free time* on [Google Groups](https://groups.google.com/forum/#!forum/ory-hydra/new) and [Gitter](https://gitter.im/ory-am/hydra). - -If you are looking for 24/7 enterprise support or SLAs, [contact us now](mailto:hello@ory.am). +Hydra is being developed by german-based company [Ory](https://ory.am). +Join our [newsletter](http://eepurl.com/bKT3N9) to stay on top of new developments. +We offer basic support requests on [Google Groups](https://groups.google.com/forum/#!forum/ory-hydra/new) and [Gitter](https://gitter.im/ory-am/hydra) +as well as [consulting](mailto:hi@ory.am) around integrating Hydra into +your particular environment and [premium support](mailto:hi@ory.am). Hydra uses the security first OAuth2 and OpenID Connect SDK [Fosite](https://github.com/ory-am/fosite) and [Ladon](https://github.com/ory-am/ladon) for policy-based access control. @@ -35,24 +37,12 @@ Hydra uses the security first OAuth2 and OpenID Connect SDK [Fosite](https://git - [Building from source](#building-from-source) - [5 minutes tutorial: Run your very own OAuth2 environment](#5-minutes-tutorial-run-your-very-own-oauth2-environment) - [Security](#security) +- [Reception](#reception) - [Documentation](#documentation) - [Guide](#guide) - - [REST API Documentation](#rest-api-documentation) - - [CLI Documentation](#cli-documentation) + - [HTTP API Documentation](#http-api-documentation) + - [Command Line Documentation](#command-line-documentation) - [Develop](#develop) -- [FAQ](#faq) - - [What is OAuth2 and what is OpenID Connect?](#what-is-oauth2-and-what-is-openid-connect) - - [Should I use OAuth2 tokens for authentication?](#should-i-use-oauth2-tokens-for-authentication) - - [Can I use Hydra in my new or existing app?](#can-i-use-hydra-in-my-new-or-existing-app) - - [I'm having trouble with the redirect URI](#im-having-trouble-with-the-redirect-uri) - - [How can I validate tokens?](#how-can-i-validate-tokens) - - [How can I import TLS certificates?](#how-can-i-import-tls-certificates) - - [I want to disable HTTPS for testing](#i-want-to-disable-https-for-testing) - - [Can I set the log level to warn, error, debug, ...?](#can-i-set-the-log-level-to-warn-error-debug-) - - [I need to use a custom CA for RethinkDB](#i-need-to-use-a-custom-ca-for-rethinkdb) - - [What will happen if an error occurs during an OAuth2 flow?](#what-will-happen-if-an-error-occurs-during-an-oauth2-flow) - - [Eventually consistent](#eventually-consistent) - - [Is there a client library / SDK?](#is-there-a-client-library--sdk) - [Hall of Fame](#hall-of-fame) @@ -89,7 +79,7 @@ Hydra is packaged using [Docker](https://hub.docker.com/r/oryam/hydra/). 6. **Open Source:** Hydra is licensed under Apache Version 2.0 7. **Professional:** Hydra implements peer reviewed open standards published by [The Internet Engineering Task Force (IETF®)](https://www.ietf.org/) and the [OpenID Foundation](https://openid.net/) and under supervision of the [LMU Teaching and Research Unit Programming and Modelling Languages](http://www.en.pms.ifi.lmu.de). No funny business. -8. **Real Time:** Operation is a lot easier with real time. There are no caches, +8. **Real Time:** Operation is a lot easier with real time. There are no caches, no invalidation strategies and no magic - just simple, cloud native pub-sub. Hydra leverages RethinkDB, so check out their real time database monitoring too!
@@ -155,109 +145,15 @@ hydra ### 5 minutes tutorial: Run your very own OAuth2 environment -In this example, you will set up Hydra, a RethinkDB instance and an exemplary identity provider written in React using docker compose. -It will take you about 5 minutes to get complete this tutorial. - -OAuth2 Flow - -Running the example - -Install the [CLI and Docker Toolbox](#installation). Make sure you install Docker Compose. On OSX and Windows, -open the Docker Quickstart Terminal. On Linux, open any terminal. - -We will use a dummy password as the system secret: `SYSTEM_SECRET=passwordtutorialpasswordtutorial`. Use a very secure secret in production. - -**On OSX and Windows** using the Docker Quickstart Terminal: -``` -$ go get github.com/ory-am/hydra -$ cd $GOPATH/src/github.com/ory-am/hydra -$ docker-compose build -Building hydra -[...] -$ SYSTEM_SECRET=passwordtutorial DOCKER_IP=$(docker-machine ip default) docker-compose up -Starting hydra_hydra_1 -[...] -``` - -**On Linux:** -``` -$ go get github.com/ory-am/hydra -$ cd $GOPATH/src/github.com/ory-am/hydra -$ docker-compose build -Building hydra -[...] -$ SYSTEM_SECRET=passwordtutorial DOCKER_IP=localhost docker-compose up -Starting hydra_rethinkdb_1 -[...] -mhydra | mtime="2016-05-17T18:09:28Z" level=warning msg="Generated system secret: MnjFP5eLIr60h?hLI1h-!<4(TlWjAHX7" -[...] -mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_id: d9227bd5-5d47-4557-957d-2fd3bee11035" -mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_secret: ,IvxGt02uNjv1ur9" -[...] -``` - -You now have a running hydra docker container! Additionally, a RethinkDB image was deployed as well as a consent app. - -Hydra can be managed with the hydra CLI client. The client has to log on before it is allowed to do anything. -When hydra detects a new installation, a new temporary root client is created. The client credentials are printed in -the container logs. +The **[tutorial](https://ory-am.gitbooks.io/hydra/content/demo.md)** teaches you to set up Hydra, +a RethinkDB instance and an exemplary identity provider written in React using docker compose. +It will take you about 5 minutes to get complete the **[tutorial](https://ory-am.gitbooks.io/hydra/content/demo.md)**. -``` -mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_id: d9227bd5-5d47-4557-957d-2fd3bee11035" -mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_secret: ,IvxGt02uNjv1ur9" -``` +OAuth2 Flow -The system secret is a global secret assigned to every hydra instance. It is used to encrypt data at rest. You can -set the system secret through the `$SYSTEM_SECRET` environment variable. When no secret is set, hydra generates one: - -``` -time="2016-05-15T14:56:34Z" level=warning msg="Generated system secret: (.UL_&77zy8/v9 -Great! You are now connected to Hydra and can start by creating a new client: - -``` -$ hydra clients create --skip-tls-verify -Client ID: c003830f-a090-4721-9463-92424270ce91 -Client Secret: Z2pJ0>Tp7.ggn>EE&rhnOzdt1 -``` - -**Important note:** if no certificate is provided, Hydra uses self-signed TLS certificates for HTTPS. This should -never be done in production. To skip the TLS verification step on the client, provide the `--skip-tls-verify` flag. - -Why not issue an access token for your client? - -``` -$ hydra token client --skip-tls-verify -JLbnRS9GQmzUBT4x7ESNw0kj2wc0ffbMwOv3QQZW4eI.qkP-IQXn6guoFew8TvaMFUD-SnAyT8GmWuqGi3wuWXg -``` - -Let's try this with the authorize code grant! - -``` -$ hydra token user --skip-tls-verify -If your browser does not open automatically, navigate to: https://192.168.99.100:4444/oauth2/... -Setting up callback listener on http://localhost:4445/callback -Press ctrl + c on Linux / Windows or cmd + c on OSX to end the process. -``` - -Great! You installed hydra, connected the CLI, created a client and completed two authentication flows! -Your next stop should be the [Guide](#guide). +
## Security @@ -267,41 +163,11 @@ OAuth2 and OAuth2 related specifications are over 200 written pages. Implementin Even if you use a secure SDK (there are numerous SDKs not secure by design in the wild), messing up the implementation is a real threat - no matter how good you or your team is. To err is human. -Let's take a look at security in Hydra: -* Hydra uses [Fosite](https://github.com/ory-am/fosite#a-word-on-security), a secure-by-design OAuth2 SDK. Fosite implements -best practices proposed by the IETF: - * [No Cleartext Storage of Credentials](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.3) - * [Encryption of Credentials](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.4) - * [Use Short Expiration Time](https://tools.ietf.org/html/rfc6819#section-5.1.5.3) - * [Limit Number of Usages or One-Time Usage](https://tools.ietf.org/html/rfc6819#section-5.1.5.4) - * [Bind Token to Client id](https://tools.ietf.org/html/rfc6819#section-5.1.5.8) - * [Automatic Revocation of Derived Tokens If Abuse Is Detected](https://tools.ietf.org/html/rfc6819#section-5.2.1.1) - * [Binding of Refresh Token to "client_id"](https://tools.ietf.org/html/rfc6819#section-5.2.2.2) - * [Refresh Token Rotation](https://tools.ietf.org/html/rfc6819#section-5.2.2.3) - * [Revocation of Refresh Tokens](https://tools.ietf.org/html/rfc6819#section-5.2.2.4) - * [Validate Pre-Registered "redirect_uri"](https://tools.ietf.org/html/rfc6819#section-5.2.3.5) - * [Binding of Authorization "code" to "client_id"](https://tools.ietf.org/html/rfc6819#section-5.2.4.4) - * [Binding of Authorization "code" to "redirect_uri"](https://tools.ietf.org/html/rfc6819#section-5.2.4.6) - * [Opaque access tokens](https://tools.ietf.org/html/rfc6749#section-1.4) - * [Opaque refresh tokens](https://tools.ietf.org/html/rfc6749#section-1.5) - * [Ensure Confidentiality of Requests](https://tools.ietf.org/html/rfc6819#section-5.1.1) - * [Use of Asymmetric Cryptography](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.5) - * **Enforcing random states:** Without a random-looking state or OpenID Connect nonce the request will fail. - * **Advanced Token Validation:** Tokens are laid out as `.` where `` is created using HMAC-SHA256 - and a global secret. This is what a token can look like: `/tgBeUhWlAT8tM8Bhmnx+Amf8rOYOUhrDi3pGzmjP7c=.BiV/Yhma+5moTP46anxMT6cWW8gz5R5vpC9RbpwSDdM=` - * **Enforcing scopes:** By default, you always need to include the `core` scope or Hydra will not execute the request. -* Hydra uses [Ladon](https://github.com/ory-am/ladon) for policy management and access control. Ladon's API is minimalistic -and well tested. -* Hydra encrypts symmetric and asymmetric keys at rest using AES-GCM 256bit. -* Hydra does not store tokens, only their signatures. An attacker gaining database access is neither able to steal tokens nor -to issue new ones. -* Hydra has automated unit and integration tests. -* Hydra does not use hacks. We would rather rewrite the whole thing instead of introducing a hack. -* APIs are uniform, well documented and secured using the warden's access control infrastructure. -* Hydra is open source and can be reviewed by anyone. -* Hydra is designed by a [security enthusiast](https://github.com/arekkas), who has written and participated in numerous auth* projects. - -Additionally to the claims above, Hydra has received a lot of positive feedback. Let's see what the community is saying: +An in-depth list of security features is listed [in the security guide](). + +## Reception + +Hydra has received a lot of positive feedback. Let's see what the community is saying: > Nice! Lowering barriers to the use of technologies like these is important. @@ -322,281 +188,37 @@ Fosite (which is what this is based on) is a very good implementation from a sec The Guide is available on [GitBook](https://ory-am.gitbooks.io/hydra/content/). -### REST API Documentation +### HTTP API Documentation -The REST API is documented at [Apiary](http://docs.hdyra.apiary.io). +The HTTP API is documented at [Apiary](http://docs.hdyra.apiary.io). -### CLI Documentation +### Command Line Documentation -The CLI help is verbose. To see it, run `hydra -h` or `hydra [command] -h`. +Run `hydra -h` or `hydra help`. ### Develop -Unless you want to test Hydra against a database, developing with Hydra is as easy as: +Developing with Hydra is as easy as: ``` go get github.com/ory-am/hydra go get github.com/Masterminds/glide cd $GOPATH/src/github.com/ory-am/hydra glide install -go test ./... -go run main.go +go test $(glide novendor) ``` -If you want to run Hydra against RethinkDB, you can do so by using docker: +If you want to run a Hydra instance, there are two possibilities: +Run without Database: ``` -docker run --name some-rethink -d -p 8080:8080 -p 28015:28015 rethinkdb - -# Linux -DATABASE_URL=rethinkdb://localhost:28015/hydra go run main.go - -# Docker Terminal -DATABASE_URL=rethinkdb://$(docker-machine ip default):28015/hydra go run main.go +go run main.go host ``` - -## FAQ - -### What is OAuth2 and what is OpenID Connect? - -* For OAuth2 explanation, I recommend reading the [Dropbox OAuth2 Guide](https://www.dropbox.com/developers/reference/oauth-guide) -* For OpenID, I recommend reading [OpenID Connect explained](http://connect2id.com/learn/openid-connect) - -### Should I use OAuth2 tokens for authentication? - -OAuth2 tokens are like money. It allows you to buy stuff, but the cashier does not really care if the money is -yours or if you stole it, as long as it's valid money. Depending on what you understand as authentication, this is a yes and no answer: - -* **Yes:** You can use access tokens to find out which user ("subject") is performing an action in a resource provider (blog article service, shopping basket, ...). -Coming back to the money example: *You*, the subject, receives a cappuccino from the vendor (resource provider) in exchange for money (access token). -* **No:** Never use access tokens for logging people in, for example `http://myapp.com/login?access_token=...`. -Coming back to the money example: The police officer ("authentication server") will not accept money ("access token") as a proof of identity ("it's really you"). Unless he is corrupt ("vulnerable"), of course. - -In the second example ("authentication server"), you must use OpenID Connect ID Tokens. - -### Can I use Hydra in my new or existing app? - -OAuth2 and OpenID Connect are tricky to understand. It is important to understand that OAuth2 is -a delegation protocol. It makes sense to use Hydra in new and existing projects. A use case covering an existing project -explains how one would use Hydra in a new one as well. So let's look at a use case! - -Let's assume we are running a ToDo List App (todo24.com). ToDo24 has a login endpoint (todo24.com/login). -The login endpoint is written in node and uses MongoDB to store user information (email + password + settings). Of course, -todo24 has other services as well: list management (todo24.com/lists/manage: close, create, move), item management (todo24.com/lists/items/manage: mark solved, add), and so on. -You are using cookies to see which user is performing the request. - -Now you decide to use OAuth2 on top of your current infrastructure. There are many reasons to do this: -* You want to open up your APIs to third-party developers. Their apps will be using OAuth2 Access Tokens to access a user's to do list. -* You want a mobile client. Because you can not store secrets on devices (they can be reverse engineered and stolen), you use OAuth2 Access Tokens instead. -* You have Cross Origin Requests. Making cookies work with Cross Origin Requests weakens or even disables important anti-CSRF measures. -* You want to write an in-browser client. This is the same case as in a mobile client (you can't store secrets in a browser). - -These are only a couple of reasons to use OAuth2. You might decide to use OAuth2 as your single source of authorization, thus maintaining -only one authorization protocol and being able to open up to third party devs in no time. With OpenID Connect, you are able to delegate authentication as well as authorization! - -Your decision is final. You want to use OAuth2 and you want Hydra to do the job. You install Hydra in your cluster using docker. -Next, you set up some exemplary OAuth2 clients. Clients can act on their own, but most of the time they need to access a user's todo lists. -To do so, the client initiates an OAuth2 request. This is where [Hydra's authentication flow](https://ory-am.gitbooks.io/hydra/content/oauth2.html#authentication-flow) comes in to play. -Before Hydra can issue an access token, we need to know WHICH user is giving consent. To do so, Hydra redirects the user agent (e.g. browser, mobile device) -to the login endpoint alongside with a challenge that contains an expiry time and other information. The login endpoint (todo24.com/login) authenticates the -user as usual, e.g. by username & password, session cookie or other means. Upon successful authentication, the login endpoint asks for the user's consent: -*"Do you want to grant MyCoolAnalyticsApp read & write access to all your todo lists? [Yes] [No]"*. Once the user clicks *Yes* and gives consent, -the login endpoint redirects back to hydra and appends something called a *consent token*. The consent token is a cryptographically signed -string that contains information about the user, specifically the user's unique id. Hydra validates the signature's trustworthiness -and issues an OAuth2 access token and optionally a refresh or OpenID token. - -Every time a request containing an access token hits a resource server (todo24.com/lists/manage), you make a request to Hydra asking who the token's -subject (the user who authorized the client to create a token on its behalf) is and whether the token is valid or not. You may optionally -ask if the token has permission to perform a certain action. - -### I'm having trouble with the redirect URI - -Hydra enforces HTTPS for all hosts except localhost. Also make sure that the path is an exact match. `http://localhost:123/` -is not the same as `http://localhost:123`. - -### How can I validate tokens? - -Please use the Warden API. There is a go client library available [here](https://github.com/ory-am/hydra/blob/master/warden/warden_http.go). -The Warden API is documented [here](http://docs.hdyra.apiary.io/#reference/warden) and [here](https://ory-am.gitbooks.io/hydra/content/policy.html). - -### How can I import TLS certificates? - -You can import TLS certificates when running `hydra host`. This can be done by setting the following environment variables: - -**Read from file** -- `HTTPS_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded). -- `HTTPS_TLS_KEY_PATH`: The path to the TLS private key (pem encoded). - -**Embedded** -- `HTTPS_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of TLS_CERT_PATH. -- `HTTPS_TLS_KEY`: A pem encoded TLS key passed as string. Can be used instead of TLS_KEY_PATH. - -Or by specifying the following flags: - -``` ---https-tls-cert-path string Path to the certificate file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead. ---https-tls-key-path string Path to the key file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead. -``` - -### I want to disable HTTPS for testing - -You can do so by running `hydra host --dangerous-force-http`. - -### Can I set the log level to warn, error, debug, ...? - -Yes, you can do so by setting the environment variable `LOG_LEVEL=`. There are various levels supported: - -* debug -* warn -* error -* fatal -* panic - -### I need to use a custom CA for RethinkDB - -You can do so by specifying environment variables: - -- `RETHINK_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded) used to connect to rethinkdb. -- `RETHINK_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of `RETHINK_TLS_CERT_PATH`. - -or via command line flag: - -``` ---rethink-tls-cert-path string Path to the certificate file to connect to rethinkdb over TLS (https). You can set RETHINK_TLS_CERT_PATH or RETHINK_TLS_CERT instead. -``` - -### What will happen if an error occurs during an OAuth2 flow? - -The user agent will either, according to spec, be redirected to the OAuth2 client who initiated the request, if possible. If not, the user agent will be redirected to the identity provider -endpoint and an `error` and `error_description` query parameter will be appended to it's URL. - -### Eventually consistent - -Using hydra with RethinkDB implies eventual consistency on all endpoints, except `/oauth2/auth` and `/oauth2/token`. -Eventual consistent data is usually not immediately available. This is dependent on the network latency between Hydra -and RethinkDB. - -### Is there a client library / SDK? - -Yes, for Go! It is available at `github.com/ory-am/hydra/sdk`. - -Connect the SDK to Hydra: -```go -import "github.com/ory-am/hydra/sdk" - -hydra, err := sdk.Connect( - sdk.ClientID("client-id"), - sdk.ClientSecret("client-secret"), - sdk.ClustURL("https://localhost:4444"), -) + +Run against RethinkDB using Docker: ``` - -Manage OAuth Clients using [`ory-am/hydra/client.HTTPManager`](/client/manager_http.go): - -```go -import "github.com/ory-am/hydra/client" - -// Create a new OAuth2 client -newClient, err := hydra.Client.CreateClient(&client.Client{ - ID: "deadbeef", - Secret: "sup3rs3cret", - RedirectURIs: []string{"http://yourapp/callback"}, - // ... -}) - -// Retrieve newly created client -newClient, err = hydra.Client.GetClient(newClient.ID) - -// Remove the newly created client -err = hydra.Client.DeleteClient(newClient.ID) - -// Retrieve list of all clients -clients, err := hydra.Client.GetClients() -``` - -Manage SSO Connections using [`ory-am/hydra/connection.HTTPManager`](connection/manager_http.go): -```go -import "github.com/ory-am/hydra/connection" - -// Create a new connection -newSSOConn, err := hydra.SSO.Create(&connection.Connection{ - Provider: "login.google.com", - LocalSubject: "bob", - RemoteSubject: "googleSubjectID", -}) - -// Retrieve newly created connection -ssoConn, err := hydra.SSO.Get(newSSOConn.ID) - -// Delete connection -ssoConn, err := hydra.SSO.Delete(newSSOConn.ID) - -// Find a connection by subject -ssoConns, err := hydra.SSO.FindAllByLocalSubject("bob") -ssoConns, err := hydra.SSO.FindByRemoteSubject("login.google.com", "googleSubjectID") -``` - -Manage policies using [`ory-am/hydra/policy.HTTPManager`](policy/manager_http.go): -```go -import "github.com/ory-am/ladon" - -// Create a new policy -// allow user to view his/her own photos -newPolicy, err := hydra.Policy.Create(&ladon.DefaultPolicy{ - ID: "1234", // ID is not required - Subjects: []string{"bob"}, - Resources: []string{"urn:media:images"}, - Actions: []string{"get", "find"}, - Effect: ladon.AllowAccess, - Conditions: ladon.Conditions{ - "owner": &ladon.EqualSubjectCondition{}, - } -}) - -// Retrieve a stored policy -policy, err := hydra.Policy.Get("1234") - -// Delete a policy -err := hydra.Policy.Delete("1234") - -// Retrieve all policies for a subject -policies, err := hydra.Policy.FindPoliciesForSubject("bob") -``` - -Manage JSON Web Keys using [`ory-am/hydra/jwk.HTTPManager`](jwk/manager_http.go): - -```go -// Generate new key set -keySet, err := hydra.JWK.CreateKeys("app-tls-keys", "HS256") - -// Retrieve key set -keySet, err := hydra.JWK.GetKeySet("app-tls-keys") - -// Delete key set -err := hydra.JWK.DeleteKeySet("app-tls-keys") -``` - -Validate requests with the Warden, uses [`ory-am/hydra/warden.HTTPWarden`](warden/warden_http.go): - -```go -import "github.com/ory-am/ladon" - -func anyHttpHandler(w http.ResponseWriter, r *http.Request) { - // Check if a token is valid and is allowed to operate given scopes - ctx, err := firewall.InspectToken(context.Background(), firewall.TokenFromRequest(r), "photos", "files") - fmt.Sprintf("%s", ctx.Subject) - - // Check if a token is valid and the token's subject fulfills the policy based access request. - ctx, err := firewall.TokenAllowed(context.Background(), "access-token", &ladon.Request{ - Resource: "matrix", - Action: "create", - Context: ladon.Context{}, - }, "photos", "files") - fmt.Sprintf("%s", ctx.Subject) -} - -// Check if request is authorized -hydra.Warden.HTTPAuthorized(ctx, req, "media.images") +docker run --name some-rethink -d -p 8080:8080 -p 28015:28015 rethinkdb +DATABASE_URL=rethinkdb://localhost:28015/hydra go run main.go host ``` ## Hall of Fame @@ -604,3 +226,4 @@ hydra.Warden.HTTPAuthorized(ctx, req, "media.images") A list of extraordinary contributors and [bug hunters](https://github.com/ory-am/hydra/issues/84). * [Alexander Widerberg (leetal)](https://github.com/leetal) for implementing the prototype RethinkDB adapters. +* The active Community on Gitter. \ No newline at end of file diff --git a/book.json b/book.json index d561db440eb..dea4c05929b 100644 --- a/book.json +++ b/book.json @@ -1,3 +1,5 @@ { - "root": "./docs/" + "root": "./docs", + "author": "Aeneas Rekkas", + "gitbook": ">=3.2.0" } \ No newline at end of file diff --git a/cmd/cli/handler_warden.go b/cmd/cli/handler_warden.go index f16f7dcfc19..034fb23b275 100644 --- a/cmd/cli/handler_warden.go +++ b/cmd/cli/handler_warden.go @@ -34,7 +34,7 @@ func (h *WardenHandler) IsAuthorized(cmd *cobra.Command, args []string) { } scopes, _ := cmd.Flags().GetStringSlice("scopes") - res, err := h.M.InspectToken(context.Background(), args[0], scopes...) + res, err := h.M.TokenValid(context.Background(), args[0], scopes...) pkg.Must(err, "Could not validate token: %s", err) out, err := json.MarshalIndent(res, "", "\t") diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..f719bac1a3a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,29 @@ +# What is [Hydra](https://github.com/ory-am/hydra)? + +At first, there was the monolith. The monolith worked well with the bespoke authentication module. +Then, the web evolved into an elastic cloud that serves thousands of different user agents +in every part of the world. + +Hydra is driven by the need for a **scalable in memory +OAuth2 and OpenID Connect** layer, that integrates with every Identity Provider you can imagine. + +Hydra is available through [Docker](https://hub.docker.com/r/oryam/hydra/) and at [GitHub](https://github.com/ory-am/hydra). + +### Feature Overview + +1. **Availability:** Hydra uses pub/sub to have the latest data available in memory. The in-memory architecture allows for heavy duty workloads. +2. **Scalability:** Hydra scales effortlessly on every platform you can imagine, including Heroku, Cloud Foundry, Docker, +Google Container Engine and many more. +3. **Integration:** Hydra wraps your existing stack like a blanket and keeps it safe. Hydra uses cryptographic tokens for authenticate users and request their consent, no APIs required. +The deprecated php-3.0 authentication service your intern wrote? It works with that too, don't worry. +We wrote an example with React to show you how this could look like: [React.js Identity Provider Example App](https://github.com/ory-am/hydra-idp-react). +4. **Security:** Hydra leverages the security first OAuth2 framework **[Fosite](https://github.com/ory-am/fosite)**, +encrypts important data at rest, and supports HTTP over TLS (https) out of the box. +5. **Ease of use:** Developers and Operators are human. Therefore, Hydra is easy to install and manage. Hydra does not care if you use React, Angular, or Cocoa for your user interface. +To support you even further, there are APIs available for *cryptographic key management, social log on, policy based access control, policy management, and two factor authentication (tbd)* +Hydra is packaged using [Docker](https://hub.docker.com/r/oryam/hydra/). +6. **Open Source:** Hydra is licensed Apache Version 2.0 +7. **Professional:** Hydra implements peer reviewed open standards published by [The Internet Engineering Task Force (IETF®)](https://www.ietf.org/) and the [OpenID Foundation](https://openid.net/) +and under supervision of the [LMU Teaching and Research Unit Programming and Modelling Languages](http://www.en.pms.ifi.lmu.de). No funny business. +8. **Real Time:** Operation is a lot easier with real time monitoring. Because Hydra leverages RethinkDB, you get real time monitoring for free. + diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 00000000000..b549cebbf5b --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,33 @@ +# Summary + +* [Introduction](README.md) +* [Basics](basics.md) + * [Architecture](basics/architecture.md) + * [Security](basics/security.md) + * [Interoperability](basics/interoperability.md) +* [5 Minutes Tutorial](demo.md) +* [Installation](install.md) +* Core Capabilities + * [OAuth2 & OpenID Connect](oauth2.md) + * [OAuth2 Basics](oauth2/basics.md) + * [OpenID Connect Basics](oauth2/openid.md) + * [Consent Flow](oauth2/consent.md) + * [JSON Web Keys](jwk.md) + * [Access Control](access-control.md) + * [Policy Introduction](access-control/policies.md) + * [The Warden](access-control/warden.md) + * [OAuth2 Token Introspection](access-control/introspection.md) + * [Manage Social Logins](sso.md) + * [SDK](sdk.md) + * [Go SDK](sdk/go.md) +* [FAQ](faq.md) + * [What does *"eventually consistent"* mean?](faq/consistency.md) + * [Where is the HTTP API Documentation?](faq/http-api.md) + * [How can I disable HTTPS for testing?](faq/disable-https.md) + * [How can I import TLS certificates?](faq/https-tls-import.md) + * [Can I set the log level to warn, error, debug, ...?](faq/log-level.md) + * [Should I use OAuth2 tokens for authentication?](faq/oauth2-auth.md) + * [What will happen if an error occurs during an OAuth2 flow?](faq/oauth2-error.md) + * [Why isn't the redirect url working?](faq/redirect-uri.md) + * [How can I import a custom CA for RethinkDB?](faq/rethink-ca.md) + * [How do I know if OAuth2 / Hydra is the right choice for me?](faq/when-use.md) diff --git a/docs/access-control.md b/docs/access-control.md new file mode 100644 index 00000000000..b3fb7f40809 --- /dev/null +++ b/docs/access-control.md @@ -0,0 +1,22 @@ +# Access Control + +Hydra offers various access control methods. Resource providers (e.g. photo/user/asset/balance/... service) use + +1. **Warden Token Validation** to validate access tokens +2. **Warden Access Control with Access Tokens** to validate access tokens and decide +if the token's subject is allowed to perform the request +3. **Warden Access Control without Access Tokens** to decide if any subject is allowed +to perform a request + +whereas third party apps (think of a facebook app) use + +1. **OAuth2 Token Introspection** to validate access tokens. + +There are two common ways to solve access control in a distributed environment (e.g. microservices). + +1. Your services are behind a gateway (e.g. access control, rate limiting, and load balancer) +that does the access control for them. This is known as a "trusted network/subnet". +2. Clients (e.g. Browser) talk to your services +directly. The services are responsible for checking access privileges themselves. + +In both cases, you would use on of the warden endpoints. diff --git a/docs/access-control/introspection.md b/docs/access-control/introspection.md new file mode 100644 index 00000000000..831361f541c --- /dev/null +++ b/docs/access-control/introspection.md @@ -0,0 +1,14 @@ +# OAuth2 Token Introspection + +OAuth2 Token Introspection is an [IETF](https://tools.ietf.org/html/rfc7662) standard. +It defines a method for a protected resource to query +an OAuth 2.0 authorization server to determine the active state of an +OAuth 2.0 token and to determine meta-information about this token. +OAuth 2.0 deployments can use this method to convey information about +the authorization context of the token from the authorization server +to the protected resource. + +In order to make a successful Token Introspection request, the audience of the access token you are introspecting +*must* match the subject of the access token you are using to access the introspection endpoint. + +The Token Introspection endpoint is documented in more detail [here](http://docs.hdyra.apiary.io/#reference/oauth2/oauth2-token-introspection). \ No newline at end of file diff --git a/docs/access-control/policies.md b/docs/access-control/policies.md new file mode 100644 index 00000000000..c9ce8299a1b --- /dev/null +++ b/docs/access-control/policies.md @@ -0,0 +1,94 @@ +# Access Control Policies + +Hydra uses the Access Control Library [Ladon](https://github.com/ory-am/ladon). +For a deep dive, it is a good idea to read the [Ladon Docs](https://github.com/ory-am/ladon#ladon). + +In Hydra, policy based access control is when you decide if: + +- Aaron (subject) is allowed (effect) to create (action) a new forum post (resource) when accessing the forum website from IP 192.168.178.3 (context). +- Richard (subject) is allowed (effect) to delete (action) a status update (resource) when he is the author (context). + +Or, more *generalized:* **Who** is **able** to do **what** on **something** with some **context**. + +* **Who (Subject)**: An arbitrary unique subject name, for example "ken" or "printer-service.mydomain.com". +* **Able (Effect)**: The effect which is always "allow" or "deny". +* **What (Action)**: An arbitrary action name, for example "delete", "create" or "scoped:action:something". +* **Something (Resource)**: An arbitrary unique resource name, for example "something", "resources:articles:1234" or some uniform resource name like "urn:isbn:3827370191". +* **Context (Context)**: The current context which may environment information like the IP Address, request date, the resource owner name, the department ken is working in and anything you like. + +Policies are JSON documents managed via the [Policy API](http://docs.hdyra.apiary.io/#reference/policies). + +``` +{ + // A required unique identifier. Used primarily for database retrieval. + "id": "68819e5a-738b-41ec-b03c-b58a1b19d043", + + // A optional human readable description. + "description": "something humanly readable", + + // A subject can be an user or a service. It is the "who" in "who is allowed to do what on something". + // As you can see here, you can use regular expressions inside < >. + "subjects": ["user", ""], + + + // Should access be allowed or denied? + // Note: If multiple policies match an access request, ladon.DenyAccess will always override ladon.AllowAccess + // and thus deny access. + "effect": "allow", + + // Which resources this policy affects. + // Again, you can put regular expressions in inside < >. + "resources": ["articles:<[0-9]+>"], + + // Which actions this policy affects. Supports RegExp + // Again, you can put regular expressions in inside < >. + "actions": ["create","update"], + + // Under which conditions this policy is "active". + "conditions": { + "owner": { + // In this example, the policy is only "active" when the requested subject is the owner of the resource as well. + "type": "EqualsSubjectCondition", + "options": {} + } + } +} +``` + +## Examples + +### Let everyone read public JWKs + +``` +[ + { + "description": "Allow everyone including anonymous users to read JSON Web Keys having Key ID *public*.", + "subject": ["<.*>"], + "effect": "allow", + "resources": [ + "rn:hydra:keys:<[^:]+>:public" + ], + "permissions": [ + "get" + ] + } +] +``` + +### Deny anyone from reading private JWKs + +``` +[ + { + "description": "Explicitly deny everyone reading JSON Web Keys with Key ID *private*.", + "subject": ["<.*>"], + "effect": "allow", + "resources": [ + "rn:hydra:keys:<[^:]+>:private" + ], + "permissions": [ + "get" + ] + } +] +``` diff --git a/dist/policies/everyone-can-read-public-keys.json b/docs/access-control/policies/everyone-can-read-public-keys.json similarity index 100% rename from dist/policies/everyone-can-read-public-keys.json rename to docs/access-control/policies/everyone-can-read-public-keys.json diff --git a/dist/policies/noone-can-read-private-keys.json b/docs/access-control/policies/noone-can-read-private-keys.json similarity index 100% rename from dist/policies/noone-can-read-private-keys.json rename to docs/access-control/policies/noone-can-read-private-keys.json diff --git a/docs/access-control/warden.md b/docs/access-control/warden.md new file mode 100644 index 00000000000..9928a288f18 --- /dev/null +++ b/docs/access-control/warden.md @@ -0,0 +1,6 @@ +## Warden + +The Warden is usually called from your own services ("resource providers"), not from third parties. Hydra prevents +third parties from having access to these endpoints per default, but you can change that with custom policies. + +The Warden endpoints are documented [here](http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers). \ No newline at end of file diff --git a/docs/basics/architecture.md b/docs/basics/architecture.md new file mode 100644 index 00000000000..d8f1d415067 --- /dev/null +++ b/docs/basics/architecture.md @@ -0,0 +1,13 @@ +## Architecture + +Hydra uses pub/sub to have the latest data always available in memory. RethinkDB makes it possible to recover from +failures and synchronize the cluster when something changes. Data is kept in memory for best performance results. +The storage layer is abstracted and can be modified to use RabbitMQ or MySQL amongst others. + +The message broker keeps the data between all host process in synch. This results in effortless `hydra host` +scaling on every platform you can imagine: Heroku, Cloud Foundry, Docker, Google Container Engine and many more. + +![](../dist/images/hydra-arch.png) + +Serving a uniform API reduces security risks. This is why all clients use REST and OAuth2 HTTP APIs. +The Command Line Interface (CLI) `hydra`, responsible for managing the cluster, uses these as well. diff --git a/docs/basics/interoperability.md b/docs/basics/interoperability.md new file mode 100644 index 00000000000..1423e199ddc --- /dev/null +++ b/docs/basics/interoperability.md @@ -0,0 +1,9 @@ +## Interoperability + +We did not want to provide you with LDAP, Active Directory, ADFS, SAML-P, SharePoint Apps, ... +integrations which probably won't work well anyway. Instead we decided to rely on cryptographic tokens +(JSON Web Tokens) for authenticating users and getting their consent. This gives you all the freedom you need with +very little effort. JSON Web Tokens are supported by all web programming languages and Hydra's +[JSON Web Key API](jwk.html) offers a nice way to deal with certificates and keys. Your users won't notice the difference. + +![OAuth2 Workflow](../dist/images/hydra-authentication.gif) \ No newline at end of file diff --git a/docs/basics/security.md b/docs/basics/security.md new file mode 100644 index 00000000000..ec1181751dc --- /dev/null +++ b/docs/basics/security.md @@ -0,0 +1,48 @@ +## Security + +> Why should I use Hydra? It's not that hard to implement two OAuth2 endpoints and there are numerous SDKs out there! + +OAuth2 and OAuth2 related specifications are over 200 written pages. Implementing OAuth2 is easy, getting it right is hard. +Even if you use a secure SDK (there are numerous SDKs not secure by design in the wild), messing up the implementation +is a real threat - no matter how good you or your team is. To err is human. Now, let us take a look at security in Hydra. + +Hydra uses [Fosite](https://github.com/ory-am/fosite#a-word-on-security), a secure-by-design OAuth2 SDK. Fosite implements +best practices proposed by the IETF: +* [No Cleartext Storage of Credentials](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.3) +* [Encryption of Credentials](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.4) +* [Use Short Expiration Time](https://tools.ietf.org/html/rfc6819#section-5.1.5.3) +* [Limit Number of Usages or One-Time Usage](https://tools.ietf.org/html/rfc6819#section-5.1.5.4) +* [Bind Token to Client id](https://tools.ietf.org/html/rfc6819#section-5.1.5.8) +* [Automatic Revocation of Derived Tokens If Abuse Is Detected](https://tools.ietf.org/html/rfc6819#section-5.2.1.1) +* [Binding of Refresh Token to "client_id"](https://tools.ietf.org/html/rfc6819#section-5.2.2.2) +* [Refresh Token Rotation](https://tools.ietf.org/html/rfc6819#section-5.2.2.3) +* [Revocation of Refresh Tokens](https://tools.ietf.org/html/rfc6819#section-5.2.2.4) +* [Validate Pre-Registered "redirect_uri"](https://tools.ietf.org/html/rfc6819#section-5.2.3.5) +* [Binding of Authorization "code" to "client_id"](https://tools.ietf.org/html/rfc6819#section-5.2.4.4) +* [Binding of Authorization "code" to "redirect_uri"](https://tools.ietf.org/html/rfc6819#section-5.2.4.6) +* [Opaque access tokens](https://tools.ietf.org/html/rfc6749#section-1.4) +* [Opaque refresh tokens](https://tools.ietf.org/html/rfc6749#section-1.5) +* [Ensure Confidentiality of Requests](https://tools.ietf.org/html/rfc6819#section-5.1.1) +* [Use of Asymmetric Cryptography](https://tools.ietf.org/html/rfc6819#section-5.1.4.1.5) +* **Enforcing random states:** Without a random-looking state or OpenID Connect nonce the request will fail. +* **Advanced Token Validation:** Tokens are laid out as `.` where `` is created using HMAC-SHA256 + and a global secret. This is what a token can look like: `/tgBeUhWlAT8tM8Bhmnx+Amf8rOYOUhrDi3pGzmjP7c=.BiV/Yhma+5moTP46anxMT6cWW8gz5R5vpC9RbpwSDdM=` +* **Enforcing scopes:** By default, you always need to include the `core` scope or Hydra will not execute the request. + +Hydra uses [Ladon](https://github.com/ory-am/ladon) for policy management and access control. Ladon's API is minimalistic +and well tested. + +Hydra encrypts symmetric and asymmetric keys at rest using AES-GCM 256bit. + +Hydra does not store tokens, only their signatures. An attacker gaining database access is neither able to steal tokens nor +to issue new ones. + +Hydra has automated unit and integration tests. + +Hydra does not use hacks. We would rather rewrite the whole thing instead of introducing a hack. + +APIs are uniform, well documented and secured using the warden's access control infrastructure. + +Hydra is open source and can be reviewed by anyone. + +Hydra is designed by a [security enthusiast](https://github.com/arekkas), who has written and participated in numerous auth* projects. diff --git a/docs/demo.md b/docs/demo.md new file mode 100644 index 00000000000..b08651f386c --- /dev/null +++ b/docs/demo.md @@ -0,0 +1,90 @@ +### 5 minutes tutorial: Run your very own OAuth2 environment + +In this example, you will set up Hydra, a RethinkDB instance and an exemplary identity provider written in React using docker compose. +It will take you about 5 minutes to get complete this tutorial. + +OAuth2 Flow + +Running the example + +Install the [CLI and Docker](https://github.com/ory-am/hydra#installation). Make sure you install Docker Compose as well. + +We will use a dummy password as the system secret: `SYSTEM_SECRET=passwordtutorialpasswordtutorial`. Use a very secure secret in production. + +``` +$ go get github.com/ory-am/hydra +$ cd $GOPATH/src/github.com/ory-am/hydra +$ docker-compose build +Building hydra +[...] +$ SYSTEM_SECRET=passwordtutorial DOCKER_IP=localhost docker-compose up +Starting hydra_rethinkdb_1 +[...] +mhydra | mtime="2016-05-17T18:09:28Z" level=warning msg="Generated system secret: MnjFP5eLIr60h?hLI1h-!<4(TlWjAHX7" +[...] +mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_id: d9227bd5-5d47-4557-957d-2fd3bee11035" +mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_secret: ,IvxGt02uNjv1ur9" +[...] +``` + +You now have a running hydra docker container! Additionally, a RethinkDB image was deployed as well as a consent app. + +Hydra can be managed with the hydra CLI client. The client has to log on before it is allowed to do anything. +When hydra detects a new installation, a new temporary root client is created. The client credentials are printed in +the container logs. + +``` +mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_id: d9227bd5-5d47-4557-957d-2fd3bee11035" +mhydra | mtime="2016-05-17T18:09:29Z" level=warning msg="client_secret: ,IvxGt02uNjv1ur9" +``` + +The system secret is a global secret assigned to every hydra instance. It is used to encrypt data at rest. You can +set the system secret through the `$SYSTEM_SECRET` environment variable. When no secret is set, hydra generates one: + +``` +time="2016-05-15T14:56:34Z" level=warning msg="Generated system secret: (.UL_&77zy8/v9Tp7.ggn>EE&rhnOzdt1 +``` + +**Important note:** if no certificate is provided, Hydra uses self-signed TLS certificates for HTTPS. This should +never be done in production. To skip the TLS verification step on the client, provide the `--skip-tls-verify` flag. + +Why not issue an access token for your client? + +``` +$ hydra token client --skip-tls-verify +JLbnRS9GQmzUBT4x7ESNw0kj2wc0ffbMwOv3QQZW4eI.qkP-IQXn6guoFew8TvaMFUD-SnAyT8GmWuqGi3wuWXg +``` + +Let's try this with the authorize code grant! + +``` +$ hydra token user --skip-tls-verify +If your browser does not open automatically, navigate to: https://192.168.99.100:4444/oauth2/... +Setting up callback listener on http://localhost:4445/callback +Press ctrl + c on Linux / Windows or cmd + c on OSX to end the process. +``` + +Great! You installed hydra, connected the CLI, created a client and completed two authentication flows! diff --git a/docs/dist/gliffy/hydra-arch.gliffy b/docs/dist/gliffy/hydra-arch.gliffy new file mode 100644 index 00000000000..298c127bdc4 --- /dev/null +++ b/docs/dist/gliffy/hydra-arch.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":373,"y":328,"rotation":0,"id":55,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":55,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-3,2],[122,2],[122,59.5],[247,59.5]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":42,"px":0,"py":0.5}}},"linkMap":[]},{"x":329.79999999999995,"y":207.5,"rotation":0,"id":48,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":80,"height":30,"lockAspectRatio":false,"lockShape":false,"order":48,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.5999999999999999,"y":0,"rotation":0,"id":50,"uid":null,"width":76.8,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Docker

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":250,"y":222.5,"rotation":0,"id":46,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":140,"height":200,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":325,"y":382,"rotation":0,"id":30,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-5,-2],[-5,105.5],[-195,105.5]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":31,"uid":null,"width":123,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

snapshots for recovery

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":11,"px":1,"py":0.5}}},"linkMap":[]},{"x":80,"y":367,"rotation":0,"id":27,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":14,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0.5],[0,83]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":32,"uid":null,"width":93,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

create snapshots

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":16,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":11,"px":0.5,"py":0}}},"linkMap":[]},{"x":197,"y":321,"rotation":0,"id":22,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":12,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[73,9],[26.333333333333314,9],[-20.333333333333343,9],[-67,9]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":23,"uid":null,"width":44,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

pub/sub

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":0,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":16,"px":1,"py":0.5}}},"linkMap":[]},{"x":30,"y":292.5,"rotation":0,"id":16,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":10,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":18,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Message Broker

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":30,"y":450,"rotation":0,"id":11,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":13,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Datastore

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":617,"y":332,"rotation":0,"id":8,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[3,-72.90620433565948],[-122,-72.90620433565948],[-122,-2],[-247,-2]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":9,"uid":null,"width":66,"height":28,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

HTTP REST\n

OAuth2

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":42,"px":1.1102230246251563e-16,"py":0.2928932188134525}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"px":1,"py":0.5}}},"linkMap":[]},{"x":270,"y":280,"rotation":0,"id":2,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.multiple_documents","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":4,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.multiple_documents.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":4,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Hydra Host

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":810,"y":62.5,"rotation":0,"id":51,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":70,"height":40,"lockAspectRatio":false,"lockShape":false,"order":51,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":53,"uid":null,"width":66,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Clients

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":665,"y":540,"rotation":0,"id":40,"uid":"com.gliffy.shape.network.network_v3.business.server","width":77,"height":120,"lockAspectRatio":true,"lockShape":false,"order":20,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.server_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":387.5,"rotation":0,"id":38,"uid":"com.gliffy.shape.network.network_v3.business.workstation_lcd","width":120,"height":120,"lockAspectRatio":true,"lockShape":false,"order":19,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.workstation_lcd_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":90,"rotation":0,"id":36,"uid":"com.gliffy.shape.network.network_v3.home.laptop","width":120,"height":120,"lockAspectRatio":true,"lockShape":false,"order":18,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.laptop_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":240,"rotation":0,"id":5,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":120,"height":100,"lockAspectRatio":false,"lockShape":false,"order":2,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.3333333333333333,"y":0,"rotation":0,"id":7,"uid":null,"width":117.33333333333336,"height":70,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"top","overflow":"none","vposition":"none","hposition":"none","html":"

\n

\n

\n

\n

Hydra CLI

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":620,"y":77.5,"rotation":0,"id":42,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":210,"height":620,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]}],"background":"#FFFFFF","width":880,"height":698,"maxWidth":5000,"maxHeight":5000,"nodeIndex":57,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.network.network_v3.home":{"fill":"#003366"},"com.gliffy.shape.network.network_v3.business":{"fill":"#003366"}},"lineStyles":{"global":{"startArrow":1,"endArrow":1}},"textStyles":{},"themeData":null}} \ No newline at end of file diff --git a/docs/dist/gliffy/hydra.gliffy b/docs/dist/gliffy/hydra.gliffy new file mode 100644 index 00000000000..7b8f95a92f2 --- /dev/null +++ b/docs/dist/gliffy/hydra.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":651.5405032467534,"y":529,"rotation":0,"id":120,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":239.4375,"height":70,"lockAspectRatio":false,"lockShape":false,"order":120,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":122,"uid":null,"width":235.4375,"height":56,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

You can use Hydra with any Identity Provider. You must implement the capability to issue consent tokens using JWT.

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":907.45,"y":224,"rotation":0,"id":117,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":110,"height":40,"lockAspectRatio":false,"lockShape":false,"order":117,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":119,"uid":null,"width":106,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Identity Provider

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":178.45000000000005,"y":273,"rotation":0,"id":112,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":112,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[2.137084989847608,2],[2.137084989847608,-90.5],[114,-90.5],[114,-163]],"lockSegments":{"1":true}}},"children":[{"x":0,"y":0,"rotation":0,"id":113,"uid":null,"width":157,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

5. Receive Access / ID Token

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":93,"px":0.7071067811865476,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":4,"px":0.5,"py":1}}},"linkMap":[]},{"x":418.01250000000005,"y":241.5,"rotation":0,"id":103,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":500,"height":380,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":760.45,"y":415,"rotation":0,"id":99,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":42,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[157.5625,-62.20057685088807],[197.5625,-62.20057685088807],[197.5625,270],[-613,270],[-613,230]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":100,"uid":null,"width":177,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

4. Return Signed Consent Token

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":103,"px":1,"py":0.29289321881345237}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":93,"px":0.5,"py":1}}},"linkMap":[]},{"x":0.537500000000037,"y":630,"rotation":0,"id":95,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":133.82500000000002,"height":30,"lockAspectRatio":false,"lockShape":false,"order":40,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.6765000000000003,"y":0,"rotation":0,"id":97,"uid":null,"width":128.47200000000004,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Authorization Service

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":67.45000000000005,"y":275,"rotation":0,"id":93,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":160,"height":370,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":221.45000000000005,"y":257,"rotation":0,"id":36,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[6,126.37049096097735],[101.28125,126.37049096097735],[101.28125,95.79942314911193],[196.56250000000006,95.79942314911193]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":57,"uid":null,"width":140,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

2. Issue Consent Request

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":93,"px":1,"py":0.29289321881345237}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":103,"px":1.1102230246251563e-16,"py":0.2928932188134525}}},"linkMap":[]},{"x":242.45000000000005,"y":76,"rotation":0,"id":34,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":12,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[5,-11],[-128.1370849898476,-11],[-128.1370849898476,199]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":55,"uid":null,"width":137,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

1. Request Access Token

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"px":0,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":93,"px":0.2928932188134524,"py":0}}},"linkMap":[]},{"x":148.2,"y":513,"rotation":0,"id":38,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":18,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-4.374999999999943,-21.44108899330365],[-4.374999999999943,-58.62739266220245],[-4.374999999999972,-95.8136963311012],[-4.374999999999972,-133]],"lockSegments":{}}},"children":[],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":26,"px":0.5,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":23,"px":0.5,"py":1}}},"linkMap":[]},{"x":87.4500000000001,"y":491.5,"rotation":0,"id":26,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":120,"height":130,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":39,"uid":null,"width":115.19999999999999,"height":112,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

\n

\n

Hydra Database (Rethink DB)\n

\n

Client Credentials, Access Tokens, Policies, ...

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":77.44999999999999,"y":290,"rotation":0,"id":23,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.multiple_documents","width":132.75000000000006,"height":90,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.multiple_documents.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.655,"y":0,"rotation":0,"id":66,"uid":null,"width":127.44000000000005,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Hydra Host

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":385.08149350649353,"y":-52,"rotation":0,"id":58,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":25,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-47.631493506493484,117],[282.9310064935065,117],[282.9310064935065,293.5]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":114,"uid":null,"width":117,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

3. Login and Consent

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":103,"px":0.5,"py":0}}},"linkMap":[]},{"x":247.45000000000005,"y":20,"rotation":0,"id":4,"uid":"com.gliffy.shape.network.network_v3.home.laptop","width":90,"height":90,"lockAspectRatio":true,"lockShape":false,"order":4,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.laptop_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":559.6967532467534,"y":425,"rotation":0,"id":81,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":38,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-4.368506493506516,-1],[-4.368506493506516,32.333333333333314],[-4.368506493506516,65.66666666666669],[-4.368506493506516,99]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":83,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

Verify Credentials

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":29,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":78,"px":0.5,"py":0}}},"linkMap":[]},{"x":505.32824675324673,"y":524,"rotation":0,"id":78,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":36,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":80,"uid":null,"width":96,"height":28,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

LDAP, MySQL, Google, ...

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":654.6967532467534,"y":346,"rotation":0,"id":76,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":35,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[0.6314935064934843,-2],[9.087662337662323,-2],[17.54383116883116,-2],[26,-2]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":29,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":67,"px":0,"py":0.5}}},"linkMap":[]},{"x":691.2592532467534,"y":304,"rotation":0,"id":72,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.text_area","width":178.875,"height":50,"lockAspectRatio":false,"lockShape":false,"order":33,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.9812500000000006,"y":0,"rotation":0,"id":74,"uid":null,"width":172.91249999999994,"height":42,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"top","overflow":"none","vposition":"none","hposition":"none","html":"

Do you want to grant app foo access to your pictures and email?

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":790.1342532467534,"y":374,"rotation":0,"id":70,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":31,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":71,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Deny\n

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":691.2592532467534,"y":374,"rotation":0,"id":68,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":29,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":69,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Allow

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":680.6967532467534,"y":264,"rotation":0,"id":67,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":200,"height":160,"lockAspectRatio":false,"lockShape":false,"order":28,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":495.32824675324684,"y":344,"rotation":0,"id":44,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.textbox","width":120,"height":20,"lockAspectRatio":false,"lockShape":false,"order":23,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":47,"uid":null,"width":116,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Password

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":495.32824675324684,"y":319,"rotation":0,"id":42,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.textbox","width":120,"height":20,"lockAspectRatio":false,"lockShape":false,"order":21,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":46,"uid":null,"width":116,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

User

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":535.3282467532468,"y":374,"rotation":0,"id":40,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":19,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":41,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

Log in

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":455.32824675324684,"y":264,"rotation":0,"id":29,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":200,"height":160,"lockAspectRatio":false,"lockShape":false,"order":2,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]}],"background":"#FFFFFF","width":1018,"height":692,"maxWidth":5000,"maxHeight":5000,"nodeIndex":124,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.network.network_v3.home":{"fill":"#003366"},"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.network.network_v3.business":{"fill":"#003366"},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"endArrow":1,"startArrow":0}},"textStyles":{},"themeData":null}} \ No newline at end of file diff --git a/docs/dist/images/abstract_flow.png b/docs/dist/images/abstract_flow.png new file mode 100644 index 00000000000..f57769ced3a Binary files /dev/null and b/docs/dist/images/abstract_flow.png differ diff --git a/docs/dist/images/consent.png b/docs/dist/images/consent.png new file mode 100644 index 00000000000..184dd779b4f Binary files /dev/null and b/docs/dist/images/consent.png differ diff --git a/docs/dist/images/google.png b/docs/dist/images/google.png new file mode 100644 index 00000000000..9ccc6e6fd12 Binary files /dev/null and b/docs/dist/images/google.png differ diff --git a/docs/dist/images/google2.png b/docs/dist/images/google2.png new file mode 100644 index 00000000000..0e908b66968 Binary files /dev/null and b/docs/dist/images/google2.png differ diff --git a/docs/dist/images/hydra-arch-warden.png b/docs/dist/images/hydra-arch-warden.png new file mode 100644 index 00000000000..890dfb7e1b9 Binary files /dev/null and b/docs/dist/images/hydra-arch-warden.png differ diff --git a/docs/dist/images/hydra-arch.png b/docs/dist/images/hydra-arch.png new file mode 100644 index 00000000000..90d396d48b4 Binary files /dev/null and b/docs/dist/images/hydra-arch.png differ diff --git a/docs/dist/images/hydra-authentication.gif b/docs/dist/images/hydra-authentication.gif new file mode 100644 index 00000000000..b6f789deebd Binary files /dev/null and b/docs/dist/images/hydra-authentication.gif differ diff --git a/docs/dist/images/login-success-a.gif b/docs/dist/images/login-success-a.gif new file mode 100644 index 00000000000..7e5c3298682 Binary files /dev/null and b/docs/dist/images/login-success-a.gif differ diff --git a/dist/logo.png b/docs/dist/images/logo.png similarity index 100% rename from dist/logo.png rename to docs/dist/images/logo.png diff --git a/dist/monitoring.gif b/docs/dist/images/monitoring.gif similarity index 100% rename from dist/monitoring.gif rename to docs/dist/images/monitoring.gif diff --git a/dist/oauth2-flow.gif b/docs/dist/images/oauth2-flow.gif similarity index 100% rename from dist/oauth2-flow.gif rename to docs/dist/images/oauth2-flow.gif diff --git a/dist/run-the-example.gif b/docs/dist/images/run-the-example.gif similarity index 100% rename from dist/run-the-example.gif rename to docs/dist/images/run-the-example.gif diff --git a/docs/dist/images/social-login-example.jpg b/docs/dist/images/social-login-example.jpg new file mode 100644 index 00000000000..e3f737d5e29 Binary files /dev/null and b/docs/dist/images/social-login-example.jpg differ diff --git a/docs/dist/images/social-login-example.png b/docs/dist/images/social-login-example.png new file mode 100644 index 00000000000..10f1ea262c9 Binary files /dev/null and b/docs/dist/images/social-login-example.png differ diff --git a/docs/dist/images/social-login.png b/docs/dist/images/social-login.png new file mode 100644 index 00000000000..f4f4016daa4 Binary files /dev/null and b/docs/dist/images/social-login.png differ diff --git a/docs/faq/consistency.md b/docs/faq/consistency.md new file mode 100644 index 00000000000..e8e6bbd41ef --- /dev/null +++ b/docs/faq/consistency.md @@ -0,0 +1,5 @@ +# Eventually consistent + +Using hydra with RethinkDB implies eventual consistency on all endpoints, except `/oauth2/auth` and `/oauth2/token`. +Eventual consistent data is usually not immediately available. This is dependent on the network latency between Hydra +and RethinkDB. \ No newline at end of file diff --git a/docs/faq/disable-https.md b/docs/faq/disable-https.md new file mode 100644 index 00000000000..75c79e8ce6e --- /dev/null +++ b/docs/faq/disable-https.md @@ -0,0 +1,3 @@ +# How can I disable HTTPS for testing? + +You can do so by running `hydra host --dangerous-force-http`. \ No newline at end of file diff --git a/docs/faq/http-api.md b/docs/faq/http-api.md new file mode 100644 index 00000000000..25362e0302b --- /dev/null +++ b/docs/faq/http-api.md @@ -0,0 +1,3 @@ +# Is there an HTTP API Documentation? + +Yes, it is available at [Apiary](http://docs.hdyra.apiary.io/). diff --git a/docs/faq/https-tls-import.md b/docs/faq/https-tls-import.md new file mode 100644 index 00000000000..97a2c8175ac --- /dev/null +++ b/docs/faq/https-tls-import.md @@ -0,0 +1,18 @@ +# How can I import TLS certificates? + +You can import TLS certificates when running `hydra host`. This can be done by setting the following environment variables: + +**Read from file** +- `HTTPS_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded). +- `HTTPS_TLS_KEY_PATH`: The path to the TLS private key (pem encoded). + +**Embedded** +- `HTTPS_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of TLS_CERT_PATH. +- `HTTPS_TLS_KEY`: A pem encoded TLS key passed as string. Can be used instead of TLS_KEY_PATH. + +Or by specifying the following flags: + +``` +--https-tls-cert-path string Path to the certificate file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead. +--https-tls-key-path string Path to the key file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead. +``` \ No newline at end of file diff --git a/docs/faq/log-level.md b/docs/faq/log-level.md new file mode 100644 index 00000000000..4663f4eea9f --- /dev/null +++ b/docs/faq/log-level.md @@ -0,0 +1,9 @@ +# Can I set the log level to warn, error, debug, ...? + +Yes, you can do so by setting the environment variable `LOG_LEVEL=`. There are various levels supported: + +* debug +* warn +* error +* fatal +* panic \ No newline at end of file diff --git a/docs/faq/oauth2-auth.md b/docs/faq/oauth2-auth.md new file mode 100644 index 00000000000..3db3c1f765f --- /dev/null +++ b/docs/faq/oauth2-auth.md @@ -0,0 +1,11 @@ +# Should I use OAuth2 tokens for authentication? + +OAuth2 tokens are like money. It allows you to buy stuff, but the cashier does not really care if the money is +yours or if you stole it, as long as it's valid money. Depending on what you understand as authentication, this is a yes and no answer: + +* **Yes:** You can use access tokens to find out which user ("subject") is performing an action in a resource provider (blog article service, shopping basket, ...). +Coming back to the money example: *You*, the subject, receives a cappuccino from the vendor (resource provider) in exchange for money (access token). +* **No:** Never use access tokens for logging people in, for example `http://myapp.com/login?access_token=...`. +Coming back to the money example: The police officer ("authentication server") will not accept money ("access token") as a proof of identity ("it's really you"). Unless he is corrupt ("vulnerable"), of course. + +In the second example ("authentication server"), you must use OpenID Connect ID Tokens. \ No newline at end of file diff --git a/docs/faq/oauth2-error.md b/docs/faq/oauth2-error.md new file mode 100644 index 00000000000..ae51b974882 --- /dev/null +++ b/docs/faq/oauth2-error.md @@ -0,0 +1,4 @@ +# What will happen if an error occurs during an OAuth2 flow? + +The user agent will either, according to spec, be redirected to the OAuth2 client who initiated the request, if possible. If not, the user agent will be redirected to the identity provider +endpoint and an `error` and `error_description` query parameter will be appended to it's URL. diff --git a/docs/faq/redirect-uri.md b/docs/faq/redirect-uri.md new file mode 100644 index 00000000000..05ec1b05435 --- /dev/null +++ b/docs/faq/redirect-uri.md @@ -0,0 +1,4 @@ +# Why isn't the redirect url working? + +Hydra enforces HTTPS for all hosts except localhost. Also make sure that the path is an exact match. `http://localhost:123/` +is not the same as `http://localhost:123`. \ No newline at end of file diff --git a/docs/faq/rethink-ca.md b/docs/faq/rethink-ca.md new file mode 100644 index 00000000000..dccfce1b559 --- /dev/null +++ b/docs/faq/rethink-ca.md @@ -0,0 +1,12 @@ +# How can I import a custom CA for RethinkDB? + +You can do so by specifying environment variables: + +- `RETHINK_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded) used to connect to rethinkdb. +- `RETHINK_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of `RETHINK_TLS_CERT_PATH`. + +or via command line flag: + +``` +--rethink-tls-cert-path string Path to the certificate file to connect to rethinkdb over TLS (https). You can set RETHINK_TLS_CERT_PATH or RETHINK_TLS_CERT instead. +``` \ No newline at end of file diff --git a/docs/faq/when-use.md b/docs/faq/when-use.md new file mode 100644 index 00000000000..30c62b53cb0 --- /dev/null +++ b/docs/faq/when-use.md @@ -0,0 +1,34 @@ +# How do I know if OAuth2 / Hydra is the right choice for me? + +OAuth2 and OpenID Connect are tricky to understand. It is important to understand that OAuth2 is +a delegation protocol. It makes sense to use Hydra in new and existing projects. A use case covering an existing project +explains how one would use Hydra in a new one as well. So let's look at a use case! + +Let's assume we are running a ToDo List App (todo24.com). ToDo24 has a login endpoint (todo24.com/login). +The login endpoint is written in node and uses MongoDB to store user information (email + password + settings). Of course, +todo24 has other services as well: list management (todo24.com/lists/manage: close, create, move), item management (todo24.com/lists/items/manage: mark solved, add), and so on. +You are using cookies to see which user is performing the request. + +Now you decide to use OAuth2 on top of your current infrastructure. There are many reasons to do this: +* You want to open up your APIs to third-party developers. Their apps will be using OAuth2 Access Tokens to access a user's to do list. +* You want a mobile client. Because you can not store secrets on devices (they can be reverse engineered and stolen), you use OAuth2 Access Tokens instead. +* You have Cross Origin Requests. Making cookies work with Cross Origin Requests weakens or even disables important anti-CSRF measures. +* You want to write an in-browser client. This is the same case as in a mobile client (you can't store secrets in a browser). + +These are only a couple of reasons to use OAuth2. You might decide to use OAuth2 as your single source of authorization, thus maintaining +only one authorization protocol and being able to open up to third party devs in no time. With OpenID Connect, you are able to delegate authentication as well as authorization! + +Your decision is final. You want to use OAuth2 and you want Hydra to do the job. You install Hydra in your cluster using docker. +Next, you set up some exemplary OAuth2 clients. Clients can act on their own, but most of the time they need to access a user's todo lists. +To do so, the client initiates an OAuth2 request. This is where [Hydra's authentication flow](https://ory-am.gitbooks.io/hydra/content/oauth2.html#authentication-flow) comes in to play. +Before Hydra can issue an access token, we need to know WHICH user is giving consent. To do so, Hydra redirects the user agent (e.g. browser, mobile device) +to the login endpoint alongside with a challenge that contains an expiry time and other information. The login endpoint (todo24.com/login) authenticates the +user as usual, e.g. by username & password, session cookie or other means. Upon successful authentication, the login endpoint asks for the user's consent: +*"Do you want to grant MyCoolAnalyticsApp read & write access to all your todo lists? [Yes] [No]"*. Once the user clicks *Yes* and gives consent, +the login endpoint redirects back to hydra and appends something called a *consent token*. The consent token is a cryptographically signed +string that contains information about the user, specifically the user's unique id. Hydra validates the signature's trustworthiness +and issues an OAuth2 access token and optionally a refresh or OpenID token. + +Every time a request containing an access token hits a resource server (todo24.com/lists/manage), you make a request to Hydra asking who the token's +subject (the user who authorized the client to create a token on its behalf) is and whether the token is valid or not. You may optionally +ask if the token has permission to perform a certain action. \ No newline at end of file diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 00000000000..13e96b93561 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,51 @@ +# Installation + +There are various ways of installing hydra on your system. + +## Download binaries + +The client and server **binaries are downloadable at [releases](https://github.com/ory-am/hydra/releases)**. +There is currently no installer available. You have to add the hydra binary to the PATH environment variable yourself or put +the binary in a location that is already in your path (`/usr/bin`, ...). +If you do not understand what that all of this means, ask in our [chat channel](https://gitter.im/ory-am/hydra). We are happy to help. + +## Using Docker + +**Starting the host** is easiest with docker. The host process handles HTTP requests and is backed by a database. +Read how to install docker on [Linux](https://docs.docker.com/linux/), [OSX](https://docs.docker.com/mac/) or +[Windows](https://docs.docker.com/windows/). Hydra is available on [Docker Hub](https://hub.docker.com/r/oryam/hydra/). + +You can use Hydra without a database, but be aware that restarting, scaling +or stopping the container will **lose all data**: + +``` +$ docker run -d -p 4444:4444 oryam/hydra --name my-hydra +ec91228cb105db315553499c81918258f52cee9636ea2a4821bdb8226872f54b +``` + +**Using the client command line interface** can be achieve by ssh'ing into the hydra container +and execute the hydra command from there: + +``` +$ docker exec -i -t /bin/bash +# e.g. docker exec -i -t ec91228 /bin/bash + +root@ec91228cb105:/go/src/github.com/ory-am/hydra# hydra +Hydra is a twelve factor OAuth2 and OpenID Connect provider + +[...] +``` + +## Building from source + +If you wish to compile hydra yourself, you need to install and set up [Go 1.5+](https://golang.org/) and add `$GOPATH/bin` +to your `$PATH`. To do so, run the following commands in a shell (bash, sh, cmd.exe, ...): + +``` +go get github.com/ory-am/hydra +go get github.com/Masterminds/glide +cd $GOPATH/src/github.com/ory-am/hydra +glide install +go install github.com/ory-am/hydra +hydra +``` diff --git a/docs/jwk.md b/docs/jwk.md new file mode 100644 index 00000000000..1294ed64bf0 --- /dev/null +++ b/docs/jwk.md @@ -0,0 +1,44 @@ +# JSON Web Keys (JWK) + +A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key and is specified at [IETF RFC7517](https://tools.ietf.org/html/rfc7517). If you've heard of PEM files... + +``` +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDg +MBQGCCqGSIb3DQMHBAgD1kGN4ZslJgSCBMi1xk9jhlPxPc +9g73NQbtqZwI+9X5OhpSg/2ALxlCCjbqvzgSu8gfFZ4yo+ +A .... MANY LINES LIKE THAT .... +X0R+meOaudPTBxoSgCCM51poFgaqt4l6VlTN4FRpj+c/Wc +blK948UAda/bWVmZjXfY4Tztah0CuqlAldOQBzu8TwE7WD +H0ga/iLNvWYexG7FHLRiq5hTj0g9mUPEbeTXuPtOkTEb/0 +GEs= +-----END ENCRYPTED PRIVATE KEY----- +``` + +... JWKs are the same, but formatted using JSON: + +``` +{ + "keys": + [ + {"kty":"oct", + "alg":"A128KW", + "k":"GawgguFyGrWKav7AX4VKUg"}, + + {"kty":"oct", + "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75 + aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", + "kid":"HMAC key used in JWS spec Appendix A.1 example"} + ] +} +``` + +Hydra offers an API for generating and managing JWKs, the [JSON Web Keys API](http://docs.hdyra.apiary.io/#reference/json-web-keys-jwk). +When using persistent storage backends, the keys are encrypted at rest using AES256-GCM and the **system secret**. +The **system secret** is generated by default and overridden by the environment variable **SYSTEM_SECRET**. + +JWKs are well supported amongst all languages. An HTTPS API takes away the pain of managing +certificates and keeps them in a safe place. **When transporting private keys over the network you +MUST encrypt ALL related traffic.** + +Please read the [API Documentation](http://docs.hdyra.apiary.io/#reference/json-web-keys-jwk) for API details. diff --git a/docs/oauth2.md b/docs/oauth2.md new file mode 100644 index 00000000000..1712dfe10d8 --- /dev/null +++ b/docs/oauth2.md @@ -0,0 +1,4 @@ +# OAuth2 + +This section covers some basic OAuth2 and OpenID Connect concepts and shows you how to integrate Hydra with your authentication +flow. diff --git a/docs/oauth2/basics.md b/docs/oauth2/basics.md new file mode 100644 index 00000000000..bc383f48500 --- /dev/null +++ b/docs/oauth2/basics.md @@ -0,0 +1,48 @@ +# OAuth2 Basics + +[This introduction was taken from the Digital Ocean Blog.](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2) + +**OAuth 2** is an authorization framework that enables applications to obtain limited access to user accounts on an +HTTP service, such as Facebook, GitHub, and Hydra. It works by delegating user authentication to the service that hosts +the user account, and authorizing third-party applications to access the user account. OAuth 2 provides authorization +flows for web and desktop applications, and mobile devices. + +![](../dist/images/abstract_flow.png) + +Here is a more detailed explanation of the steps in the diagram: + +* The application requests authorization to access service resources from the user +* If the user authorized the request, the application receives an authorization grant +* The application requests an access token from the authorization server (API) by presenting authentication of its own +identity, and the authorization grant. +* If the application identity is authenticated and the authorization grant is valid, the authorization server (API) +issues an access token to the application. Authorization is complete. +* The application requests the resource from the resource server (API) and presents the access token for authentication. +* If the access token is valid, the resource server (API) serves the resource to the application. + +The actual flow of this process will differ depending on the authorization grant type in use, but this is the general idea. + +Read more on OAuth2 on [the Digital Ocean Blog](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2). +We also recommend reading [API Security: Deep Dive into OAuth and OpenID Connect](http://nordicapis.com/api-security-oauth-openid-connect-depth/). + +**Glossary** +* **The resource owner** is the user who authorizes an application to access their account. The application's access to +the user's account is limited to the "scope" of the authorization granted (e.g. read or write access). +* **Authorization Server (Hydra)** verifies the identity of the user and issues access tokens to the *client application*. +* **Client** is the *application* that wants to access the user's account. Before it may do so, it must be authorized +by the user. +* **Identity Provider** contains a log in user interface and a database of all your users. To integrate Hydra, +you must modify the Identity Provider. It mus be able to generate consent tokens and ask for the user's consent. +* **User Agent** is usually the resource owner's browser. +* **Consent Endpoint** is an app (e.g. NodeJS) that is able to receive consent challenges and create consent tokens. +It must verify the identity of the user that is giving the consent. This can be achieved using Cookie Auth, +HTTP Basic Auth, Login HTML Form, or any other mean of authentication. Upon authentication, the user must be asked +if he consents to allowing the client access to his resources. + +## OAuth2 Clients + +We already covered some basic OAuth2 concepts [in the Introduction](introduction.html). +You can manage *clients* using the cli or the HTTP REST API. + +* **CLI:** `hydra clients -h` +* **REST:** Read the [API Docs](http://docs.hdyra.apiary.io/#reference/oauth2-clients) diff --git a/docs/oauth2/consent.md b/docs/oauth2/consent.md new file mode 100644 index 00000000000..d903d9e4131 --- /dev/null +++ b/docs/oauth2/consent.md @@ -0,0 +1,109 @@ +# Consent Flow + +Hydra does not ship user authentication. This is something you will have to solve yourself. Usually when you are looking +at using OAuth2 for your app, you already have user authentication anyways. + +In abstract, a consent flow looks like this: + +![](../dist/images/consent.png) + +1. A *client* application (app in browser in laptop) requests an access token from a resource owner: `https://hydra.myapp.com/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=vboeidlizlxrywkwlsgeggff&nonce=tedgziijemvninkuotcuuiof`. +2. Hydra generates a consent challenge and forwards the *user agent* (browser in laptop) to the *consent endpoint*: `https://login.myapp.com/?challenge=eyJhbGciOiJSUzI1N...`. +3. The *consent endpoint* verifies the resource owner's identity (e.g. cookie, username/password login form, ...). The consent challenge is then decoded and the information extracted. It is used to show the consent screen: `Do you want to grant _my cool app_ access to all your private data? [Yes] [No]` +4. When consent is given, the *consent endpoint* generates a consent token and redirects the user agent (browser in laptop) back to hydra: `https://hydra.myapp.com/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=vboeidlizlxrywkwlsgeggff&nonce=tedgziijemvninkuotcuuiof&consent=eyJhbGciOiJSU...`. +5. Hydra validates the consent token and issues the access token to the *user agent*. + +## Detailed Example + +In this section we assume that hydra runs on `https://192.168.99.100:4444` and our consent app on `https:/192.168.99.100:3000`. + +All user-based OAuth2 requests, including the OpenID Connect workflow, begin at the `/oauth2/auth` endpoint. For example: `https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=wewuphkgywhtldsmainefkyx&nonce=uqfjjzftqpjccdvxltaposri` + +If the request includes a valid redirect uri and a valid client id, hydra redirects the user to then consent url. The consent url can be set using the `$CONSENT_URL` environment variable. + +Let's set the `$CONSENT_URL` to `https:/192.168.99.100:3000/consent`, where a NodeJS application is running (the *consent app*). Next, Hydra appends a consent challenge to the consent url and redirects the user to it. For example: `http://192.168.99.100:3000/?challenge=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTU0ODIsImp0aSI6IjNmYWRlN2NjLTdlYTItNGViMi05MGI1LWY5OTUwNTI4MzgyOSIsInJlZGlyIjoiaHR0cHM6Ly8xOTIuMTY4Ljk5LjEwMDo0NDQ0L29hdXRoMi9hdXRoP2NsaWVudF9pZD1jM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NThcdTAwMjZyZXNwb25zZV90eXBlPWNvZGVcdTAwMjZzY29wZT1jb3JlK2h5ZHJhXHUwMDI2c3RhdGU9d2V3dXBoa2d5d2h0bGRzbWFpbmVma3l4XHUwMDI2bm9uY2U9dXFmamp6ZnRxcGpjY2R2eGx0YXBvc3JpIiwic2NwIjpbImNvcmUiLCJoeWRyYSJdfQ.KpLBotIEE4izVSAjLOeCCfm_wYZ7UWSCA81akr6Ci1yycKs8e_bhBYdSThy8JW3bAvofNcZ0v48ov9KxZVegWm8GuNbBEcNvKeiyW_8PiJXWE92YsMv-tDIL3VFPOp0469FmDLsSg5ohsFj5S89FzykNYfVxLPBAFcAS_JElWbo` + +The consent challenge is a signed RSA-SHA 256 (RS256) [JSON Web Token](https://tools.ietf.org/html/rfc7519) and contains the following claims: + + +``` +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksImp0aSI6IjY0YzRmNzllLWUwMTYtNDViOC04YzBlLWQ5NmM2NzFjMWU4YSIsInJlZGlyIjoiaHR0cHM6Ly8xOTIuMTY4Ljk5LjEwMDo0NDQ0L29hdXRoMi9hdXRoP2NsaWVudF9pZD1jM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NThcdTAwMjZyZXNwb25zZV90eXBlPWNvZGVcdTAwMjZzY29wZT1jb3JlK2h5ZHJhXHUwMDI2c3RhdGU9bXlobnhxbXd6aHRleWN3ZW92Ymxzd3dqXHUwMDI2bm9uY2U9Z21tc3V2dHNidG9ldW1lb2hlc3p0c2hnIiwic2NwIjpbImNvcmUiLCJoeWRyYSJdfQ.v4K1-AuT5Uwu1DRNvdf7SwjjPT8KO97thRYa3pDWzjBLyjkCNvgp0P5V0oA3XqRutoFpYx4AtQyz0bY7n3XcPE7ZQ2nBWTBnZ04GzWbxcJNFhBvgc_jiQBECebdxN29kgxHoU0frtVDcz6Uur468nBa9D_BDBpN-KgEBsI5Hjhc + +{ + "aud": "c3b49cf0-88e4-4faa-9489-28d5b8957858", + "exp": 1464515099, + "jti": "64c4f79e-e016-45b8-8c0e-d96c671c1e8a", + "redir": "https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=myhnxqmwzhteycweovblswwj&nonce=gmmsuvtsbtoeumeohesztshg", + "scp": [ + "core", + "hydra" + ] +} +``` + +The challenge claims are: +* **jti:** A unique id. +* **scp:** The requested scopes, e.g. `["blog.readall", "blog.writeall"]` +* **aud:** The client id that initiated the request. You can fetch client data using the [OAuth2 Client API](http://docs.hdyra.apiary.io/#reference/oauth2/manage-the-oauth2-client-collection). +* **exp:** The challenge's expiry date. Consent endpoints must not accept challenges that have expired. +* **redir:** Where the consent endpoint should redirect the user agent to, once consent is given. + +Hydra signs the consent token with a key called consent.challenge. +The public key can be looked up via the [Key Manager](https://ory-am.gitbooks.io/hydra/content/jwk.html): + +``` +https://192.168.99.100:4444/keys/consent.challenge/public +``` + +Next, the consent-app must check if the user is authenticated. This can be done by e.g. using a session cookie. +If the user is not authenticate, he must be challenged to provide valid credentials through e.g. a HTML form. +The consent-app could use LDAP, MySQL, RethinkDB or any other backend to store and verify the credentials. + +Upon user authentication, the consent-app must ask for the user's consent. This could look like: + +> _That super useful service app_ would like to: +> * Know who you are +> * View your extended profile info +> * Get read access to all your cloud pictures +> +> [Deny] - [Allow] + +If the user clicks *Allow*, the consent-app redirects him back to the *redir* claim value. The consent-app appends +a signed consent token to the URL: + +``` +https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=myhnxqmwzhteycweovblswwj&nonce=gmmsuvtsbtoeumeohesztshg&consent=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksInNjcCI6WyJjb3JlIiwiaHlkcmEiXSwic3ViIjoiam9obi5kb2VAbWUuY29tIiwiaWF0IjoxNDY0NTExNTE1fQ.tX5TKdP9hHCgPbqBzKIYMjJVwqOdxf5ACScmQ6t20Qteo8AYEfavGwq8KxRF1Oz_otcQDdZY--jcl1caom0yT2eTvj1d9E2Hs7eXmYuW_xF9pTpmDwJnrcOlONFKsNZN97n41qprzMrsX5ez0T5AcopGwpPMxKhwGDSXq9CQgQU +``` + +The consent token is a RSA-SHA 256 (RS256) signed [JSON Web Token](https://tools.ietf.org/html/rfc7519) +that contains the following claims: + +``` +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksInNjcCI6WyJjb3JlIiwiaHlkcmEiXSwic3ViIjoiam9obi5kb2VAbWUuY29tIiwiaWF0IjoxNDY0NTExNTE1fQ.tX5TKdP9hHCgPbqBzKIYMjJVwqOdxf5ACScmQ6t20Qteo8AYEfavGwq8KxRF1Oz_otcQDdZY--jcl1caom0yT2eTvj1d9E2Hs7eXmYuW_xF9pTpmDwJnrcOlONFKsNZN97n41qprzMrsX5ez0T5AcopGwpPMxKhwGDSXq9CQgQU + +{ + "aud": "c3b49cf0-88e4-4faa-9489-28d5b8957858", + "exp": 1464515099, + "scp": [ + "core", + "hydra" + ], + "sub": "john.doe@me.com", + "iat": 1464511515, + "id_ext": { "foo": "bar" }, + "at_ext": { "baz": true } +} +``` + +The consent claims are: +* **jti:** A unique id. +* **scp:** The scopes the user opted in to *grant* access to, e.g. only `["blog.readall"]`. +* **aud:** The client id that initiated the OAuth2 request. You can fetch client data using the [OAuth2 Client API](http://docs.hdyra.apiary.io/#reference/oauth2/manage-the-oauth2-client-collection). +* **exp:** The expiry date of this token. Use very short lifespans (< 5 min). +* **iat:** The tokens issuance time. +* **id_ext:** If set, pass this extra data to the id token *(optional)* +* **at_ext:** If set, pass this extra data to the access token session. You can retrieve the data by using the warden endpoints *(optional)*. + +Hydra validates the consent token with consent-app's public key. The public key must be stored in the (https://ory-am.gitbooks.io/hydra/content/key_manager.html) at `https://localhost:4444/keys/consent.endpoint/public` + +If you want, you can use the Key Manager to store and retrieve private keys as well. When Hydra boots for the first time, a private/public `consent.endpoint` keypair is created. You can that keypair to sign consent tokens. The private key is available at `https://localhost:4444/keys/asymmetric/consent.endpoint/private`. diff --git a/docs/oauth2/openid.md b/docs/oauth2/openid.md new file mode 100644 index 00000000000..f9ff3a48c53 --- /dev/null +++ b/docs/oauth2/openid.md @@ -0,0 +1,17 @@ +# OpenID Connect 1.0 + +**OpenID Connect 1.0** is a simple identity layer on top of the OAuth 2.0 protocol. +It allows Clients to verify the identity of the End-User based on the authentication performed +by an Authorization Server, as well as to obtain basic profile information about the End-User in an +interoperable and REST-like manner. + +OpenID Connect allows clients of all types, including Web-based, mobile, and JavaScript clients, +to request and receive information about authenticated sessions and end-users. The specification +suite is extensible, allowing participants to use optional features such as encryption of identity data, +discovery of OpenID Providers, and session management, when it makes sense for them. + +There are different work flows for OpenID Connect 1.0, we recommend checking out the OpenID Connect sandbox at +[openidconnect.net](https://openidconnect.net/). + +In a nutshell, add `openid` to the OAuth2 scope when making an OAuth2 Authorize Code request. +You will receive an `id_token` alongside the `access_token` when making the code exchange. \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 00000000000..bf25e726ff4 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,3 @@ +# Overview + +This section gives you a high level overview of Hydra's core concepts and its architecture. \ No newline at end of file diff --git a/docs/sdk.md b/docs/sdk.md new file mode 100644 index 00000000000..4475a22f25a --- /dev/null +++ b/docs/sdk.md @@ -0,0 +1,3 @@ +# SDK + +An SDK is available for Golang via `go get github.com/ory-am/hydra/sdk`. We are planning on implementing SDKs for other languages too. diff --git a/docs/sdk/go.md b/docs/sdk/go.md new file mode 100644 index 00000000000..fbad7141233 --- /dev/null +++ b/docs/sdk/go.md @@ -0,0 +1,120 @@ +## Go SDK + +Connect the SDK to Hydra: +```go +import "github.com/ory-am/hydra/sdk" + +hydra, err := sdk.Connect( + sdk.ClientID("client-id"), + sdk.ClientSecret("client-secret"), + sdk.ClustURL("https://localhost:4444"), +) +``` + +Manage OAuth Clients using [`ory-am/hydra/client.HTTPManager`](/client/manager_http.go): + +```go +import "github.com/ory-am/hydra/client" + +// Create a new OAuth2 client +newClient, err := hydra.Client.CreateClient(&client.Client{ + ID: "deadbeef", + Secret: "sup3rs3cret", + RedirectURIs: []string{"http://yourapp/callback"}, + // ... +}) + +// Retrieve newly created client +newClient, err = hydra.Client.GetClient(newClient.ID) + +// Remove the newly created client +err = hydra.Client.DeleteClient(newClient.ID) + +// Retrieve list of all clients +clients, err := hydra.Client.GetClients() +``` + +Manage SSO Connections using [`ory-am/hydra/connection.HTTPManager`](connection/manager_http.go): +```go +import "github.com/ory-am/hydra/connection" + +// Create a new connection +newSSOConn, err := hydra.SSO.Create(&connection.Connection{ + Provider: "login.google.com", + LocalSubject: "bob", + RemoteSubject: "googleSubjectID", +}) + +// Retrieve newly created connection +ssoConn, err := hydra.SSO.Get(newSSOConn.ID) + +// Delete connection +ssoConn, err := hydra.SSO.Delete(newSSOConn.ID) + +// Find a connection by subject +ssoConns, err := hydra.SSO.FindAllByLocalSubject("bob") +ssoConns, err := hydra.SSO.FindByRemoteSubject("login.google.com", "googleSubjectID") +``` + +Manage policies using [`ory-am/hydra/policy.HTTPManager`](policy/manager_http.go): +```go +import "github.com/ory-am/ladon" + +// Create a new policy +// allow user to view his/her own photos +newPolicy, err := hydra.Policy.Create(&ladon.DefaultPolicy{ + ID: "1234", // ID is not required + Subjects: []string{"bob"}, + Resources: []string{"urn:media:images"}, + Actions: []string{"get", "find"}, + Effect: ladon.AllowAccess, + Conditions: ladon.Conditions{ + "owner": &ladon.EqualSubjectCondition{}, + } +}) + +// Retrieve a stored policy +policy, err := hydra.Policy.Get("1234") + +// Delete a policy +err := hydra.Policy.Delete("1234") + +// Retrieve all policies for a subject +policies, err := hydra.Policy.FindPoliciesForSubject("bob") +``` + +Manage JSON Web Keys using [`ory-am/hydra/jwk.HTTPManager`](jwk/manager_http.go): + +```go +// Generate new key set +keySet, err := hydra.JWK.CreateKeys("app-tls-keys", "HS256") + +// Retrieve key set +keySet, err := hydra.JWK.GetKeySet("app-tls-keys") + +// Delete key set +err := hydra.JWK.DeleteKeySet("app-tls-keys") +``` + +Validate requests with the Warden, uses [`ory-am/hydra/warden.HTTPWarden`](warden/warden_http.go): + +```go +import "github.com/ory-am/ladon" + +func anyHttpHandler(w http.ResponseWriter, r *http.Request) { + // Check if a token is valid and is allowed to operate given scopes + ctx, err := firewall.TokenValid(context.Background(), firewall.TokenFromRequest(r), "photos", "files") + fmt.Sprintf("%s", ctx.Subject) + + // Check if a token is valid and the token's subject fulfills the policy based access request. + ctx, err := firewall.TokenAllowed(context.Background(), "access-token", &ladon.Request{ + Resource: "matrix", + Action: "create", + Context: ladon.Context{}, + }, "photos", "files") + fmt.Sprintf("%s", ctx.Subject) +} + +// Check if request is authorized +hydra.Warden.HTTPAuthorized(ctx, req, "media.images") +``` \ No newline at end of file diff --git a/docs/sso.md b/docs/sso.md new file mode 100644 index 00000000000..c83b76b00c4 --- /dev/null +++ b/docs/sso.md @@ -0,0 +1,39 @@ +# Social Login Management + +> Social login, also known as social sign-in, is a form of single sign-on using existing login information from a social +networking service such as Facebook, Twitter or Google+ to sign into a third party website instead of creating +a new login account specifically for that website. It is designed to simplify logins for end users as well as +provide more and more reliable demographic information to web developers. *- [Source: Wikipedia](https://en.wikipedia.org/wiki/Social_login)* + +It is important to note, that Hydra supports you in managing Social Login capabilities, +but does not handle Social Login itself. + +## Exemplary Social Login Journey + +The log in screen + +![](dist/images/social-login-example.jpg) + +Logging in with Google Account +![](dist/images/google.png) + +User authorizes access +![](dist/images/google2.png) + +![](dist/images/social-login-example.jpg) + +Login completed +![](dist/images/login-success-a.gif) + +## In The Background + +Depending on the third party's APIs you complete the sign in request with OAuth 1.0, +OAuth2, OpenID Connect, or some other flow. In any case, you will receive (e.g. /userinfo, id token, ...) +a user id from that service, e.g. `googleuser:u398fjka8f2hj28g`. We call this value the **remote subject**, +the login provider (e.g. Google) **provider**, and the users stored in your private MySQL/LDAP/... +database **local subjects**. + +You can pass the provider and the remote subject values to the +[Social Login API](http://docs.hdyra.apiary.io/#reference/social-login-management) and look up if one of your local +subjects is linked to that third party account. If there is a match you can use the local subject value +to identify and authenticate the user. If there is no match, you will probably send him to your sign up page. \ No newline at end of file diff --git a/firewall/warden.go b/firewall/warden.go index 2449d7f8532..1c22507ab71 100644 --- a/firewall/warden.go +++ b/firewall/warden.go @@ -13,37 +13,35 @@ import ( type Context struct { // Subject is the identity that authorized issuing the token, for example a user or an OAuth2 app. // This is usually a uuid but you can choose a urn or some other id too. - Subject string `json:"sub"` + Subject string `json:"sub"` // GrantedScopes is a list of scopes that the subject authorized when asked for consent. - GrantedScopes []string `json:"scopes"` + GrantedScopes []string `json:"scopes"` // Issuer is the id of the issuer, typically an hydra instance. - Issuer string `json:"iss"` + Issuer string `json:"iss"` // Audience is who the token was issued for. This is an OAuth2 app usually. - Audience string `json:"aud"` + Audience string `json:"aud"` // IssuedAt is the token creation time stamp. - IssuedAt time.Time `json:"iat"` + IssuedAt time.Time `json:"iat"` // ExpiresAt is the expiry timestamp. - ExpiresAt time.Time `json:"exp"` + ExpiresAt time.Time `json:"exp"` // Extra represents arbitrary session data. - Extra map[string]interface{} `json:"ext"` + Extra map[string]interface{} `json:"ext"` } // Firewall offers various validation strategies for access tokens. type Firewall interface { - Introspector - - // InspectToken checks if the given token is valid and if the requested scopes are satisfied. Returns + // TokenValid checks if the given token is valid and if the requested scopes are satisfied. Returns // a context if the token is valid and an error if not. // - // ctx, err := firewall.InspectToken(context.Background(), "access-token", "photos", "files") + // ctx, err := firewall.TokenValid(context.Background(), "access-token", "photos", "files") // fmt.Sprintf("%s", ctx.Subject) - InspectToken(ctx context.Context, token string, scopes ...string) (*Context, error) + TokenValid(ctx context.Context, token string, scopes ...string) (*Context, error) // IsAllowed uses policies to return nil if the access request can be fulfilled or an error if not. // @@ -71,72 +69,8 @@ type Firewall interface { // TokenFromRequest returns an access token from the HTTP Authorization header. // // func anyHttpHandler(w http.ResponseWriter, r *http.Request) { - // ctx, err := firewall.InspectToken(context.Background(), firewall.TokenFromRequest(r), "photos", "files") + // ctx, err := firewall.TokenValid(context.Background(), firewall.TokenFromRequest(r), "photos", "files") // fmt.Sprintf("%s", ctx.Subject) // } TokenFromRequest(r *http.Request) string } - -// Introspection contains an access token's session data as specified by IETF RFC 7662. -type Introspection struct { - // Active is a boolean indicator of whether or not the presented token - // is currently active. The specifics of a token's "active" state - // will vary depending on the implementation of the authorization - // server and the information it keeps about its tokens, but a "true" - // value return for the "active" property will generally indicate - // that a given token has been issued by this authorization server, - // has not been revoked by the resource owner, and is within its - // given time window of validity (e.g., after its issuance time and - // before its expiration time). - Active bool `json:"active"` - - // Scope is a JSON string containing a space-separated list of - // scopes associated with this token - Scope string `json:"scope,omitempty"` - - // ClientID is aclient identifier for the OAuth 2.0 client that - // requested this token. - ClientID string `json:"client_id,omitempty"` - - // Subject of the token, as defined in JWT [RFC7519]. - // Usually a machine-readable identifier of the resource owner who - // authorized this token. - Subject string `json:"sub,omitempty"` - - // Expires at is an integer timestamp, measured in the number of seconds - // since January 1 1970 UTC, indicating when this token will expire - ExpiresAt int64 `json:"exp,omitempty"` - - // Issued at is an integer timestamp, measured in the number of seconds - // since January 1 1970 UTC, indicating when this token was - // originally issued - IssuedAt int64 `json:"iat,omitempty"` - - // NotBefore is an integer timestamp, measured in the number of seconds - // since January 1 1970 UTC, indicating when this token is not to be - // used before - NotBefore int64 `json:"nbf,omitempty"` - - // Username is a human-readable identifier for the resource owner who - // authorized this token. - Username int64 `json:"username,omitempty"` - - // Audience is a service-specific string identifier or list of string - // identifiers representing the intended audience for this token - Audience string `json:"aud,omitempty"` - - // Issuer is a string representing the issuer of this token - Issuer string `json:"iss,omitempty"` -} - -// Introspector is capable of introspecting an access token according to IETF RFC 7662, see: -// https://tools.ietf.org/html/rfc7662 -type Introspector interface { - // IntrospectToken performs a token introspection according to IETF RFC 7662, see: https://tools.ietf.org/html/rfc7662 - // - // func anyHttpHandler(w http.ResponseWriter, r *http.Request) { - // ctx, err := firewall.InspectToken(context.Background(), firewall.TokenFromRequest(r), "photos", "files") - // fmt.Sprintf("%s", ctx.Subject) - // } - IntrospectToken(ctx context.Context, token string) (*Introspection, error) -} diff --git a/oauth2/consent_strategy.go b/oauth2/consent_strategy.go index 722310599ce..3786a38d1c7 100644 --- a/oauth2/consent_strategy.go +++ b/oauth2/consent_strategy.go @@ -71,7 +71,7 @@ func (s *DefaultConsentStrategy) ValidateResponse(a fosite.AuthorizeRequester, t if ext, ok := t.Claims["id_ext"].(map[string]interface{}); ok { idExt = ext } - if ext, ok := t.Claims["id_ext"].(map[string]interface{}); ok { + if ext, ok := t.Claims["at_ext"].(map[string]interface{}); ok { atExt = ext } diff --git a/oauth2/handler.go b/oauth2/handler.go index be6b020b30e..38500ade435 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -7,36 +7,77 @@ import ( "github.com/go-errors/errors" "github.com/julienschmidt/httprouter" "github.com/ory-am/fosite" + "github.com/ory-am/hydra/herodot" "github.com/ory-am/hydra/pkg" + "github.com/ory-am/hydra/firewall" ) const ( OpenIDConnectKeyName = "hydra.openid.connect" + + ConsentPath = "/oauth2/consent" + TokenPath = "/oauth2/token" + AuthPath = "/oauth2/auth" + + // IntrospectPath points to the OAuth2 introspection endpoint. + IntrospectPath = "/oauth2/introspect" ) type Handler struct { - OAuth2 fosite.OAuth2Provider - Consent ConsentStrategy - ForcedHTTP bool + OAuth2 fosite.OAuth2Provider + Consent ConsentStrategy + + Introspector Introspector + Firewall firewall.Firewall + H herodot.Herodot + + ForcedHTTP bool + ConsentURL url.URL +} - ConsentURL url.URL +func (this *Handler) SetRoutes(r *httprouter.Router) { + r.POST(TokenPath, this.TokenHandler) + r.GET(AuthPath, this.AuthHandler) + r.POST(AuthPath, this.AuthHandler) + r.GET(ConsentPath, this.DefaultConsentHandler) + r.POST(IntrospectPath, this.Introspect) } -func (h *Handler) SetRoutes(r *httprouter.Router) { - r.POST("/oauth2/token", h.TokenHandler) - r.GET("/oauth2/auth", h.AuthHandler) - r.POST("/oauth2/auth", h.AuthHandler) - r.GET("/oauth2/consent", h.DefaultConsentHandler) +func (this *Handler) Introspect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + var inactive = map[string]bool{"active": false} + + ctx := herodot.NewContext() + clientCtx, err := this.Firewall.TokenValid(ctx, this.Firewall.TokenFromRequest(r)) + if err != nil { + this.H.WriteError(ctx, w, r, err) + return + } + + if err := r.ParseForm(); err != nil { + this.H.WriteError(ctx, w, r, err) + return + } + + auth, err := this.Introspector.IntrospectToken(ctx, r.PostForm.Get("token")) + if err != nil { + this.H.Write(ctx, w, r, &inactive) + return + } else if clientCtx.Subject != auth.Audience { + this.H.Write(ctx, w, r, &inactive) + return + } + + this.H.Write(ctx, w, r, auth) } -func (o *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +func (this *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { var session = NewSession("") var ctx = fosite.NewContext() - accessRequest, err := o.OAuth2.NewAccessRequest(ctx, r, session) + accessRequest, err := this.OAuth2.NewAccessRequest(ctx, r, session) if err != nil { pkg.LogError(err) - o.OAuth2.WriteAccessError(w, accessRequest, err) + this.OAuth2.WriteAccessError(w, accessRequest, err) return } @@ -49,23 +90,23 @@ func (o *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprou } } - accessResponse, err := o.OAuth2.NewAccessResponse(ctx, r, accessRequest) + accessResponse, err := this.OAuth2.NewAccessResponse(ctx, r, accessRequest) if err != nil { pkg.LogError(err) - o.OAuth2.WriteAccessError(w, accessRequest, err) + this.OAuth2.WriteAccessError(w, accessRequest, err) return } - o.OAuth2.WriteAccessResponse(w, accessRequest, accessResponse) + this.OAuth2.WriteAccessResponse(w, accessRequest, accessResponse) } -func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { +func (this *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { var ctx = fosite.NewContext() - authorizeRequest, err := o.OAuth2.NewAuthorizeRequest(ctx, r) + authorizeRequest, err := this.OAuth2.NewAuthorizeRequest(ctx, r) if err != nil { pkg.LogError(err) - o.writeAuthorizeError(w, authorizeRequest, err) + this.writeAuthorizeError(w, authorizeRequest, err) return } @@ -73,9 +114,9 @@ func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprout consentToken := authorizeRequest.GetRequestForm().Get("consent") if consentToken == "" { // otherwise redirect to log in endpoint - if err := o.redirectToConsent(w, r, authorizeRequest); err != nil { + if err := this.redirectToConsent(w, r, authorizeRequest); err != nil { pkg.LogError(err) - o.writeAuthorizeError(w, authorizeRequest, err) + this.writeAuthorizeError(w, authorizeRequest, err) return } return @@ -83,36 +124,36 @@ func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprout // decode consent_token claims // verify anti-CSRF (inject state) and anti-replay token (expiry time, good value would be 10 seconds) - session, err := o.Consent.ValidateResponse(authorizeRequest, consentToken) + session, err := this.Consent.ValidateResponse(authorizeRequest, consentToken) if err != nil { pkg.LogError(err) - o.writeAuthorizeError(w, authorizeRequest, errors.New(fosite.ErrAccessDenied)) + this.writeAuthorizeError(w, authorizeRequest, errors.New(fosite.ErrAccessDenied)) return } // done - response, err := o.OAuth2.NewAuthorizeResponse(ctx, r, authorizeRequest, session) + response, err := this.OAuth2.NewAuthorizeResponse(ctx, r, authorizeRequest, session) if err != nil { pkg.LogError(err) - o.writeAuthorizeError(w, authorizeRequest, err) + this.writeAuthorizeError(w, authorizeRequest, err) return } - o.OAuth2.WriteAuthorizeResponse(w, authorizeRequest, response) + this.OAuth2.WriteAuthorizeResponse(w, authorizeRequest, response) } -func (o *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, authorizeRequest fosite.AuthorizeRequester) error { +func (this *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, authorizeRequest fosite.AuthorizeRequester) error { schema := "https" - if o.ForcedHTTP { + if this.ForcedHTTP { schema = "http" } - challenge, err := o.Consent.IssueChallenge(authorizeRequest, schema+"://"+r.Host+r.URL.String()) + challenge, err := this.Consent.IssueChallenge(authorizeRequest, schema+"://"+r.Host+r.URL.String()) if err != nil { return err } - p := o.ConsentURL + p := this.ConsentURL q := p.Query() q.Set("challenge", challenge) p.RawQuery = q.Encode() @@ -120,11 +161,11 @@ func (o *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, auth return nil } -func (o *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.AuthorizeRequester, err error) { +func (this *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.AuthorizeRequester, err error) { if !ar.IsRedirectURIValid() { var rfcerr = fosite.ErrorToRFC6749Error(err) - redirectURI := o.ConsentURL + redirectURI := this.ConsentURL query := redirectURI.Query() query.Add("error", rfcerr.Name) query.Add("error_description", rfcerr.Description) @@ -135,5 +176,5 @@ func (o *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.Authorize return } - o.OAuth2.WriteAuthorizeError(w, ar, err) + this.OAuth2.WriteAuthorizeError(w, ar, err) } diff --git a/oauth2/introspector.go b/oauth2/introspector.go new file mode 100644 index 00000000000..5f11e10e965 --- /dev/null +++ b/oauth2/introspector.go @@ -0,0 +1,71 @@ +package oauth2 + +import "golang.org/x/net/context" + +// Introspection contains an access token's session data as specified by IETF RFC 7662, see: +// https://tools.ietf.org/html/rfc7662 +type Introspection struct { + // Active is a boolean indicator of whether or not the presented token + // is currently active. The specifics of a token's "active" state + // will vary depending on the implementation of the authorization + // server and the information it keeps about its tokens, but a "true" + // value return for the "active" property will generally indicate + // that a given token has been issued by this authorization server, + // has not been revoked by the resource owner, and is within its + // given time window of validity (e.g., after its issuance time and + // before its expiration time). + Active bool `json:"active"` + + // Scope is a JSON string containing a space-separated list of + // scopes associated with this token. + Scope string `json:"scope,omitempty"` + + // ClientID is aclient identifier for the OAuth 2.0 client that + // requested this token. + ClientID string `json:"client_id,omitempty"` + + // Subject of the token, as defined in JWT [RFC7519]. + // Usually a machine-readable identifier of the resource owner who + // authorized this token. + Subject string `json:"sub,omitempty"` + + // Expires at is an integer timestamp, measured in the number of seconds + // since January 1 1970 UTC, indicating when this token will expire. + ExpiresAt int64 `json:"exp,omitempty"` + + // Issued at is an integer timestamp, measured in the number of seconds + // since January 1 1970 UTC, indicating when this token was + // originally issued. + IssuedAt int64 `json:"iat,omitempty"` + + // NotBefore is an integer timestamp, measured in the number of seconds + // since January 1 1970 UTC, indicating when this token is not to be + // used before. + NotBefore int64 `json:"nbf,omitempty"` + + // Username is a human-readable identifier for the resource owner who + // authorized this token. + Username int64 `json:"username,omitempty"` + + // Audience is a service-specific string identifier or list of string + // identifiers representing the intended audience for this token. + Audience string `json:"aud,omitempty"` + + // Issuer is a string representing the issuer of this token + Issuer string `json:"iss,omitempty"` + + // Extra is arbitrary data set by the session. + Extra map[string]interface{} `json:"ext,omitempty"` +} + +// Introspector is capable of introspecting an access token according to IETF RFC 7662, see: +// https://tools.ietf.org/html/rfc7662 +type Introspector interface { + // IntrospectToken performs a token introspection according to IETF RFC 7662, see: https://tools.ietf.org/html/rfc7662 + // + // func anyHttpHandler(w http.ResponseWriter, r *http.Request) { + // ctx, err := introspector.IntrospectToken(context.Background(), introspector.TokenFromRequest(r), "photos", "files") + // fmt.Sprintf("%s", ctx.Subject) + // } + IntrospectToken(ctx context.Context, token string) (*Introspection, error) +} diff --git a/oauth2/introspector_http.go b/oauth2/introspector_http.go new file mode 100644 index 00000000000..8e328150f78 --- /dev/null +++ b/oauth2/introspector_http.go @@ -0,0 +1,64 @@ +package oauth2 + +import ( + "net/url" + "bytes" + "io/ioutil" + "net/http" + "golang.org/x/net/context" + "strconv" + "encoding/json" + "github.com/ory-am/fosite" + "golang.org/x/oauth2/clientcredentials" + "golang.org/x/oauth2" + "github.com/go-errors/errors" +) + +type HTTPIntrospector struct { + Client *http.Client + Dry bool + Endpoint *url.URL +} + +func (this *HTTPIntrospector) TokenFromRequest(r *http.Request) string { + return fosite.AccessTokenFromRequest(r) +} + +func (this *HTTPIntrospector) SetClient(c *clientcredentials.Config) { + this.Client = c.Client(oauth2.NoContext) +} + +// IntrospectToken is capable of introspecting tokens according to https://tools.ietf.org/html/rfc7662 +// +// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/oauth2/oauth2-token-introspection +func (this *HTTPIntrospector) IntrospectToken(ctx context.Context, token string) (*Introspection, error) { + var resp = new(Introspection) + var ep = *this.Endpoint + ep.Path = IntrospectPath + + data := url.Values{"token": []string{token}} + hreq, err := http.NewRequest("POST", ep.String(), bytes.NewBufferString(data.Encode())) + if err != nil { + return nil, errors.New(err) + } + + hreq.Header.Add("Content-Type", "application/x-www-form-urlencoded") + hreq.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + hres, err := this.Client.Do(hreq) + if err != nil { + return nil, errors.New(err) + } + defer hres.Body.Close() + + if hres.StatusCode < 200 || hres.StatusCode >= 300 { + body, _ := ioutil.ReadAll(hres.Body) + return nil, errors.Errorf("Expected 2xx status code but got %d.\n%s", hres.StatusCode, body) + } else if err := json.NewDecoder(hres.Body).Decode(resp); err != nil { + body, _ := ioutil.ReadAll(hres.Body) + return nil, errors.Errorf("%s: %s", err, body) + } else if !resp.Active { + return nil, errors.New("Token is malformed, expired or otherwise invalid") + } + + return resp, nil +} \ No newline at end of file diff --git a/oauth2/introspector_local.go b/oauth2/introspector_local.go new file mode 100644 index 00000000000..8c5ecf2d57e --- /dev/null +++ b/oauth2/introspector_local.go @@ -0,0 +1,45 @@ +package oauth2 + +import ( + "github.com/ory-am/fosite" + "time" + "strings" + "net/http" + "golang.org/x/net/context" + "github.com/Sirupsen/logrus" +) + +type LocalIntrospector struct { + OAuth2 fosite.OAuth2Provider + + AccessTokenLifespan time.Duration + Issuer string +} + +func (w *LocalIntrospector) TokenFromRequest(r *http.Request) string { + return fosite.AccessTokenFromRequest(r) +} + +func (w *LocalIntrospector) IntrospectToken(ctx context.Context, token string) (*Introspection, error) { + var session = new(Session) + var auth, err = w.OAuth2.ValidateToken(ctx, token, fosite.AccessToken, session) + if err != nil { + logrus.WithError(err).Infof("Token introspection failed") + return &Introspection{ + Active: false, + }, err + } + + session = auth.GetSession().(*Session) + return &Introspection{ + Active: true, + Subject: session.Subject, + Audience: auth.GetClient().GetID(), + Scope: strings.Join(auth.GetGrantedScopes(), " "), + Issuer: w.Issuer, + IssuedAt: auth.GetRequestedAt().Unix(), + NotBefore: auth.GetRequestedAt().Unix(), + ExpiresAt: session.AccessTokenExpiresAt(auth.GetRequestedAt().Add(w.AccessTokenLifespan)).Unix(), + Extra: session.Extra, + }, nil +} \ No newline at end of file diff --git a/oauth2/introspector_test.go b/oauth2/introspector_test.go new file mode 100644 index 00000000000..ca1be2e5dca --- /dev/null +++ b/oauth2/introspector_test.go @@ -0,0 +1,158 @@ +package oauth2_test + +import ( + "testing" + "time" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" + "github.com/ory-am/hydra/pkg" + "net/http/httptest" + "net/url" + "github.com/ory-am/hydra/warden" + "github.com/ory-am/fosite" + "github.com/julienschmidt/httprouter" + "github.com/ory-am/hydra/herodot" + foauth2 "github.com/ory-am/fosite/handler/oauth2" + goauth2 "golang.org/x/oauth2" + "github.com/Sirupsen/logrus" + "github.com/ory-am/hydra/oauth2" + "github.com/ory-am/ladon" +) + +var ( + introspectors = make(map[string]oauth2.Introspector) + now = time.Now().Round(time.Second) + tokens = pkg.Tokens(3) + fositeStore = pkg.FositeStore() +) + +var ladonWarden = pkg.LadonWarden(map[string]ladon.Policy{ + "1": &ladon.DefaultPolicy{ + ID: "1", + Subjects: []string{"alice"}, + Resources: []string{"matrix", "rn:hydra:token<.*>"}, + Actions: []string{"create", "decide"}, + Effect: ladon.AllowAccess, + }, + "2": &ladon.DefaultPolicy{ + ID: "2", + Subjects: []string{"siri"}, + Resources: []string{"<.*>"}, + Actions: []string{"decide"}, + Effect: ladon.AllowAccess, + }, +}) + +var localWarden = &warden.LocalWarden{ + Warden: ladonWarden, + OAuth2: &fosite.Fosite{ + Store: fositeStore, + TokenValidators: fosite.TokenValidators{ + &foauth2.CoreValidator{ + CoreStrategy: pkg.HMACStrategy, + CoreStorage: fositeStore, + ScopeStrategy: fosite.HierarchicScopeStrategy, + }, + }, + ScopeStrategy: fosite.HierarchicScopeStrategy, + }, + Issuer: "tests", + AccessTokenLifespan: time.Hour, +} + +func init() { + introspectors["local"] = &oauth2.LocalIntrospector{ + OAuth2: localWarden.OAuth2, + Issuer: "tests", + AccessTokenLifespan: time.Hour, + } + + r := httprouter.New() + serv := &oauth2.Handler{ + Firewall: localWarden, + H: &herodot.JSON{}, + Introspector: introspectors["local"], + } + serv.SetRoutes(r) + ts = httptest.NewServer(r) + + ar := fosite.NewAccessRequest(oauth2.NewSession("alice")) + ar.GrantedScopes = fosite.Arguments{"core"} + ar.RequestedAt = now + ar.Client = &fosite.DefaultClient{ID: "siri"} + ar.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"} + fositeStore.CreateAccessTokenSession(nil, tokens[0][0], ar) + + ar2 := fosite.NewAccessRequest(oauth2.NewSession("siri")) + ar2.GrantedScopes = fosite.Arguments{"core"} + ar2.RequestedAt = now + ar2.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"} + ar2.Client = &fosite.DefaultClient{ID: "siri"} + fositeStore.CreateAccessTokenSession(nil, tokens[1][0], ar2) + + ar3 := fosite.NewAccessRequest(oauth2.NewSession("siri")) + ar3.GrantedScopes = fosite.Arguments{"core"} + ar3.RequestedAt = now + ar2.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"} + ar3.Client = &fosite.DefaultClient{ID: "doesnt-exist"} + ar3.Session.(*oauth2.Session).AccessTokenExpiry = time.Now().Add(-time.Hour) + fositeStore.CreateAccessTokenSession(nil, tokens[2][0], ar3) + + conf := &goauth2.Config{ + Scopes: []string{}, + Endpoint: goauth2.Endpoint{}, + } + + ep, err := url.Parse(ts.URL) + if err != nil { + logrus.Fatalf("%s", err) + } + introspectors["http"] = &oauth2.HTTPIntrospector{ + Endpoint: ep, + Client: conf.Client(goauth2.NoContext, &goauth2.Token{ + AccessToken: tokens[1][1], + Expiry: time.Now().Add(time.Hour), + TokenType: "bearer", + }), + } +} + +func TestIntrospect(t *testing.T) { + for _, w := range introspectors { + for _, c := range []struct { + token string + expectErr bool + assert func(*oauth2.Introspection) + }{ + { + token: "invalid", + expectErr: true, + }, + { + token: tokens[2][1], + expectErr: true, + }, + { + token: tokens[1][1], + expectErr: false, + }, + { + token: tokens[0][1], + expectErr: false, + assert: func(c *oauth2.Introspection) { + assert.Equal(t, "alice", c.Subject) + assert.Equal(t, "tests", c.Issuer) + assert.Equal(t, now.Add(time.Hour).Unix(), c.ExpiresAt, "expires at") + assert.Equal(t, now.Unix(), c.IssuedAt, "issued at") + assert.Equal(t, map[string]interface{}{"foo": "bar"}, c.Extra) + }, + }, + } { + ctx, err := w.IntrospectToken(context.Background(), c.token) + pkg.AssertError(t, c.expectErr, err) + if err == nil && c.assert != nil { + c.assert(ctx) + } + } + } +} diff --git a/policy/doc.go b/policy/doc.go new file mode 100644 index 00000000000..9712c4c0609 --- /dev/null +++ b/policy/doc.go @@ -0,0 +1,15 @@ +// Package policy offers management capabilities for access control policies. +// To read up on policies, go to: +// +// - https://github.com/ory-am/ladon +// +// - https://ory-am.gitbooks.io/hydra/content/policy.html +// +// Contains source files: +// +// - handler.go: A HTTP handler capable of managing policies. +// +// - warden_http.go: A Go API using HTTP to validate managing policies. +// +// - warden_test.go: Functional tests all of the above. +package policy diff --git a/sdk/client.go b/sdk/client.go index 908fa27460d..9708395a7e3 100644 --- a/sdk/client.go +++ b/sdk/client.go @@ -30,19 +30,19 @@ var defaultOptions = []option{ // Client offers easy use of all HTTP clients. type Client struct { // Client offers OAuth2 Client management capabilities. - Client *client.HTTPManager + Client *client.HTTPManager // SSO offers Social Login management capabilities. - SSO *connection.HTTPManager + SSO *connection.HTTPManager // JWK offers JSON Web Key management capabilities. - JWK *jwk.HTTPManager + JWK *jwk.HTTPManager // Policies offers Access Policy management capabilities. Policies *policy.HTTPManager // Warden offers Access Token and Access Request validation strategies. - Warden *warden.HTTPWarden + Warden *warden.HTTPWarden http *http.Client clusterURL *url.URL @@ -50,10 +50,18 @@ type Client struct { clientSecret string skipTLSVerify bool scopes []string - credentials clientcredentials.Config + credentials clientcredentials.Config } // Connect instantiates a new client to communicate with Hydra. +// +// import "github.com/ory-am/hydra/sdk" +// +// var hydra, err = sdk.Connect( +// sdk.ClientID("client-id"), +// sdk.ClientSecret("client-secret"), +// sdk.ClusterURL("https://localhost:4444"), +// ) func Connect(opts ...option) (*Client, error) { c := &Client{} @@ -125,6 +133,26 @@ func Connect(opts ...option) (*Client, error) { return c, nil } +// OAuth2Config returns an oauth2 config instance which you can use to initiate various oauth2 flows. +// +// config := client.OAuth2Config("https://mydomain.com/oauth2_callback", "photos", "contacts.read") +// redirectRequestTo := oauth2.AuthCodeURL() +// +// // in callback handler... +// token, err := config.Exchange(oauth2.NoContext, authorizeCode) +func (h *Client) OAuth2Config(redirectURL string, scopes ...string) *oauth2.Config { + return &oauth2.Config{ + ClientSecret: h.clientSecret, + ClientID: h.clientID, + Endpoint: oauth2.Endpoint{ + TokenURL: pkg.JoinURL(h.clusterURL, "/oauth2/token").String(), + AuthURL: pkg.JoinURL(h.clusterURL, "/oauth2/auth").String(), + }, + Scopes: scopes, + RedirectURL: redirectURL, + } +} + func (h *Client) authenticate() error { ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, h.http) _, err := h.credentials.Token(ctx) diff --git a/sdk/client_opts.go b/sdk/client_opts.go index a48572ac0bd..37c0e12fad6 100644 --- a/sdk/client_opts.go +++ b/sdk/client_opts.go @@ -7,15 +7,6 @@ import ( "gopkg.in/yaml.v2" ) -// ClusterURL sets Hydra service URL -func ClusterURL(urlStr string) option { - return func(c *Client) error { - var err error - c.clusterURL, err = url.Parse(urlStr) - return err - } -} - type hydraConfig struct { ClusterURL string `yaml:"cluster_url"` ClientID string `yaml:"client_id"` @@ -50,7 +41,25 @@ func FromYAML(file string) option { } } -// ClientID sets OAuth client ID +// ClusterURL sets Hydra service URL +// +// var hydra, err = sdk.Connect( +// sdk.ClientID("https://localhost:1234/"), +// ) +func ClusterURL(urlStr string) option { + return func(c *Client) error { + var err error + c.clusterURL, err = url.Parse(urlStr) + return err + } +} + + +// ClientID sets the OAuth2 Client ID. +// +// var hydra, err = sdk.Connect( +// sdk.ClientID("client-id"), +// ) func ClientID(id string) option { return func(c *Client) error { c.clientID = id @@ -58,7 +67,11 @@ func ClientID(id string) option { } } -// ClientSecret sets OAuth client secret +// ClientSecret sets OAuth2 Client secret. +// +// var hydra, err = sdk.Connect( +// sdk.ClientSecret("client-secret"), +// ) func ClientSecret(secret string) option { return func(c *Client) error { c.clientSecret = secret @@ -66,7 +79,11 @@ func ClientSecret(secret string) option { } } -// SkipTLSVerify skips TLS verification +// SkipTLSVerify skips TLS verification for HTTPS connections. +// +// var hydra, err = sdk.Connect( +// sdk.SkipTLSVerify(), +// ) func SkipTLSVerify() option { return func(c *Client) error { c.skipTLSVerify = true @@ -74,7 +91,11 @@ func SkipTLSVerify() option { } } -// Scopes sets client scopes granted by Hydra +// Scopes is a list of scopes that are requested in the client credentials grant. +// +// var hydra, err = sdk.Connect( +// sdk.Scopes("foo", "bar"), +// ) func Scopes(scopes ...string) option { return func(c *Client) error { c.scopes = scopes diff --git a/sdk/client_opts_test.go b/sdk/client_opts_test.go index 3d49d2871af..749b735c689 100644 --- a/sdk/client_opts_test.go +++ b/sdk/client_opts_test.go @@ -6,7 +6,6 @@ import ( "testing" "gopkg.in/yaml.v2" - "github.com/stretchr/testify/assert" ) diff --git a/sdk/doc.go b/sdk/doc.go index a0705ed21b3..2c1fe2eab48 100644 --- a/sdk/doc.go +++ b/sdk/doc.go @@ -2,10 +2,11 @@ // // import "github.com/ory-am/hydra/sdk" // import "github.com/ory-am/hydra/client" +// // var hydra, err = sdk.Connect( // sdk.ClientID("client-id"), // sdk.ClientSecret("client-secret"), -// sdk.ClustURL("https://localhost:4444"), +// sdk.ClusterURL("https://localhost:4444"), // ) // // // You now have access to the various API endpoints of hydra, for example the oauth2 client endpoint: diff --git a/warden/doc.go b/warden/doc.go index caf9144758a..0c9f8245c13 100644 --- a/warden/doc.go +++ b/warden/doc.go @@ -1,9 +1,18 @@ // Package warden decides if access requests should be allowed or denied. In a scientific taxonomy, the warden // is classified as a Policy Decision Point. THe warden's primary goal is to implement `github.com/ory-am/hydra/firewall.Firewall`. +// To read up on the warden, go to: // -// This package is structured as follows: -// * handler.go: A HTTP handler capable of validating access tokens. -// * warden_http.go: A Go API using HTTP to validate access tokens. -// * warden_local.go: A Go API using storage managers to validate access tokens. -// * warden_test.go: Functional tests all of the above. +// - https://ory-am.gitbooks.io/hydra/content/policy.html +// +// - http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers +// +// Contains source files: +// +// - handler.go: A HTTP handler capable of validating access tokens. +// +// - warden_http.go: A Go API using HTTP to validate access tokens. +// +// - warden_local.go: A Go API using storage managers to validate access tokens. +// +// - warden_test.go: Functional tests all of the above. package warden diff --git a/warden/handler.go b/warden/handler.go index ab3144c713b..199c9eb7c7c 100644 --- a/warden/handler.go +++ b/warden/handler.go @@ -14,37 +14,24 @@ import ( ) const ( - TokenValidHandlerPath = "/warden/token/valid" - TokenAllowedHandlerPath = "/warden/token/allowed" - AllowedHandlerPath = "/warden/allowed" - IntrospectPath = "/oauth2/introspect" -) - -type WardenHandler struct { - H herodot.Herodot - Warden firewall.Firewall -} + // TokenValidHandlerPath points to the token validation endpoint. + TokenValidHandlerPath = "/warden/token/valid" -func NewHandler(c *config.Config, router *httprouter.Router) *WardenHandler { - ctx := c.Context() - - h := &WardenHandler{ - H: &herodot.JSON{}, - Warden: ctx.Warden, - } - h.SetRoutes(router) + // TokenAllowedHandlerPath points to the token access request validation endpoint. + TokenAllowedHandlerPath = "/warden/token/allowed" - return h -} + // AllowedHandlerPath points to the access request validation endpoint. + AllowedHandlerPath = "/warden/allowed" +) -type WardenAuthorizedRequest struct { +type wardenAuthorizedRequest struct { Scopes []string `json:"scopes"` Token string `json:"token"` } -type WardenAccessRequest struct { +type wardenAccessRequest struct { *ladon.Request - *WardenAuthorizedRequest + *wardenAuthorizedRequest } var notAllowed = struct { @@ -55,40 +42,28 @@ var invalid = struct { Valid bool `json:"valid"` }{Valid: false} -var inactive = struct { - Active bool `json:"active"` -}{Active: false} - -func (h *WardenHandler) SetRoutes(r *httprouter.Router) { - r.POST(TokenValidHandlerPath, h.TokenValid) - r.POST(TokenAllowedHandlerPath, h.TokenAllowed) - r.POST(AllowedHandlerPath, h.Allowed) - r.POST(IntrospectPath, h.Introspect) +// WardenHandler is capable of handling HTTP request and validating access tokens and access requests. +type WardenHandler struct { + H herodot.Herodot + Warden firewall.Firewall } -func (h *WardenHandler) Introspect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - ctx := herodot.NewContext() - clientCtx, err := h.Warden.InspectToken(ctx, TokenFromRequest(r)) - if err != nil { - h.H.WriteError(ctx, w, r, err) - return - } +func NewHandler(c *config.Config, router *httprouter.Router) *WardenHandler { + ctx := c.Context() - if err := r.ParseForm(); err != nil { - h.H.WriteError(ctx, w, r, err) - return + h := &WardenHandler{ + H: &herodot.JSON{}, + Warden: ctx.Warden, } + h.SetRoutes(router) - auth, err := h.Warden.IntrospectToken(ctx, r.PostForm.Get("token")) - if err != nil { - h.H.Write(ctx, w, r, &inactive) - return - } else if clientCtx.Subject != auth.Audience { - h.H.Write(ctx, w, r, &inactive) - return - } + return h +} - h.H.Write(ctx, w, r, auth) +func (h *WardenHandler) SetRoutes(r *httprouter.Router) { + r.POST(TokenValidHandlerPath, h.TokenValid) + r.POST(TokenAllowedHandlerPath, h.TokenAllowed) + r.POST(AllowedHandlerPath, h.Allowed) } func (h *WardenHandler) TokenValid(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -102,14 +77,14 @@ func (h *WardenHandler) TokenValid(w http.ResponseWriter, r *http.Request, _ htt return } - var ar WardenAuthorizedRequest + var ar wardenAuthorizedRequest if err := json.NewDecoder(r.Body).Decode(&ar); err != nil { h.H.WriteError(ctx, w, r, err) return } defer r.Body.Close() - authContext, err := h.Warden.InspectToken(ctx, ar.Token, ar.Scopes...) + authContext, err := h.Warden.TokenValid(ctx, ar.Token, ar.Scopes...) if err != nil { h.H.Write(ctx, w, r, &invalid) return @@ -163,9 +138,9 @@ func (h *WardenHandler) TokenAllowed(w http.ResponseWriter, r *http.Request, _ h return } - var ar = WardenAccessRequest{ + var ar = wardenAccessRequest{ Request: new(ladon.Request), - WardenAuthorizedRequest: new(WardenAuthorizedRequest), + wardenAuthorizedRequest: new(wardenAuthorizedRequest), } if err := json.NewDecoder(r.Body).Decode(&ar); err != nil { h.H.WriteError(ctx, w, r, errors.New(err)) diff --git a/warden/warden_http.go b/warden/warden_http.go index 780f18e20c6..7a29caff35e 100644 --- a/warden/warden_http.go +++ b/warden/warden_http.go @@ -4,11 +4,6 @@ import ( "net/http" "net/url" - "bytes" - "encoding/json" - "io/ioutil" - "strconv" - "github.com/go-errors/errors" "github.com/ory-am/fosite" "github.com/ory-am/hydra/firewall" @@ -33,44 +28,10 @@ func (w *HTTPWarden) SetClient(c *clientcredentials.Config) { w.Client = c.Client(oauth2.NoContext) } -func (w *HTTPWarden) IntrospectToken(ctx context.Context, token string) (*firewall.Introspection, error) { - var resp = new(firewall.Introspection) - var ep = *w.Endpoint - ep.Path = IntrospectPath - agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client} - - data := url.Values{"token": []string{token}} - hreq, err := http.NewRequest("POST", ep.String(), bytes.NewBufferString(data.Encode())) - if err != nil { - return nil, errors.New(err) - } - - hreq.Header.Add("Content-Type", "application/x-www-form-urlencoded") - hreq.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) - hres, err := w.Client.Do(hreq) - if err != nil { - return nil, errors.New(err) - } - - if hres.StatusCode < 200 || hres.StatusCode >= 300 { - body, _ := ioutil.ReadAll(hres.Body) - return nil, errors.Errorf("Expected 2xx status code but got %d.\n%s", hres.StatusCode, body) - } else if err := json.NewDecoder(hres.Body).Decode(resp); err != nil { - body, _ := ioutil.ReadAll(hres.Body) - return nil, errors.Errorf("%s: %s", err, body) - } - - if err := agent.POST(&struct { - Token string `json:"token"` - }{Token: token}, &hres); err != nil { - return nil, err - } else if !resp.Active { - return nil, errors.New("Token is malformed, expired or otherwise invalid") - } - - return resp, nil -} - +// TokenAllowed checks if a token is valid and if the token owner is allowed to perform an action on a resource. +// This endpoint requires a token, a scope, a resource name, an action name and a context. +// +// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-an-access-tokens-subject-is-allowed-to-do-something func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Request, scopes ...string) (*firewall.Context, error) { var resp = struct { *firewall.Context @@ -80,8 +41,8 @@ func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Re var ep = *w.Endpoint ep.Path = TokenAllowedHandlerPath agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client} - if err := agent.POST(&WardenAccessRequest{ - WardenAuthorizedRequest: &WardenAuthorizedRequest{ + if err := agent.POST(&wardenAccessRequest{ + wardenAuthorizedRequest: &wardenAuthorizedRequest{ Token: token, Scopes: scopes, }, @@ -95,6 +56,9 @@ func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Re return resp.Context, nil } +// IsAllowed checks if an arbitrary subject is allowed to perform an action on a resource. +// +// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-a-subject-is-allowed-to-do-something func (w *HTTPWarden) IsAllowed(ctx context.Context, a *ladon.Request) error { var allowed = struct { Allowed bool `json:"allowed"` @@ -112,7 +76,10 @@ func (w *HTTPWarden) IsAllowed(ctx context.Context, a *ladon.Request) error { return nil } -func (w *HTTPWarden) InspectToken(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) { +// TokenValid checks if an access token is valid. You must provide a token and a scope. +// +// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-an-access-token-is-valid +func (w *HTTPWarden) TokenValid(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) { var resp = struct { *firewall.Context Valid bool `json:"valid"` @@ -121,7 +88,7 @@ func (w *HTTPWarden) InspectToken(ctx context.Context, token string, scopes ...s var ep = *w.Endpoint ep.Path = TokenValidHandlerPath agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client} - if err := agent.POST(&WardenAuthorizedRequest{ + if err := agent.POST(&wardenAuthorizedRequest{ Token: token, Scopes: scopes, }, &resp); err != nil { diff --git a/warden/warden_local.go b/warden/warden_local.go index b0c1bc28639..cfde59b03de 100644 --- a/warden/warden_local.go +++ b/warden/warden_local.go @@ -5,8 +5,6 @@ import ( "time" - "strings" - "github.com/Sirupsen/logrus" "github.com/go-errors/errors" "github.com/ory-am/fosite" @@ -17,8 +15,8 @@ import ( ) type LocalWarden struct { - Warden ladon.Warden - OAuth2 fosite.OAuth2Provider + Warden ladon.Warden + OAuth2 fosite.OAuth2Provider AccessTokenLifespan time.Duration Issuer string @@ -28,29 +26,6 @@ func (w *LocalWarden) TokenFromRequest(r *http.Request) string { return fosite.AccessTokenFromRequest(r) } -func (w *LocalWarden) IntrospectToken(ctx context.Context, token string) (*firewall.Introspection, error) { - var session = new(oauth2.Session) - var auth, err = w.OAuth2.ValidateToken(ctx, token, fosite.AccessToken, session) - if err != nil { - logrus.WithError(err).Infof("Token introspection failed") - return &firewall.Introspection{ - Active: false, - }, err - } - - session = auth.GetSession().(*oauth2.Session) - return &firewall.Introspection{ - Active: true, - Subject: session.Subject, - Audience: auth.GetClient().GetID(), - Scope: strings.Join(auth.GetGrantedScopes(), " "), - Issuer: w.Issuer, - IssuedAt: auth.GetRequestedAt().Unix(), - NotBefore: auth.GetRequestedAt().Unix(), - ExpiresAt: session.AccessTokenExpiresAt(auth.GetRequestedAt().Add(w.AccessTokenLifespan)).Unix(), - }, nil -} - func (w *LocalWarden) IsAllowed(ctx context.Context, a *ladon.Request) error { if err := w.Warden.IsAllowed(a); err != nil { logrus.WithFields(logrus.Fields{ @@ -79,7 +54,7 @@ func (w *LocalWarden) TokenAllowed(ctx context.Context, token string, a *ladon.R return w.allowed(ctx, a, scopes, auth, session) } -func (w *LocalWarden) InspectToken(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) { +func (w *LocalWarden) TokenValid(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) { var session = new(oauth2.Session) var oauthRequest = fosite.NewAccessRequest(session) diff --git a/warden/warden_test.go b/warden/warden_test.go index 60ef22957ae..3df88a0fc29 100644 --- a/warden/warden_test.go +++ b/warden/warden_test.go @@ -211,41 +211,6 @@ func TestActionAllowed(t *testing.T) { } } -func TestIntrospect(t *testing.T) { - for n, w := range wardens { - for k, c := range []struct { - token string - expectErr bool - assert func(*firewall.Introspection) - }{ - { - token: "invalid", - expectErr: true, - }, - { - token: tokens[2][1], - expectErr: true, - }, - { - token: tokens[0][1], - expectErr: false, - assert: func(c *firewall.Introspection) { - assert.Equal(t, "alice", c.Subject) - assert.Equal(t, "tests", c.Issuer) - assert.Equal(t, now.Add(time.Hour).Unix(), c.ExpiresAt) - assert.Equal(t, now.Unix(), c.IssuedAt) - }, - }, - } { - ctx, err := w.IntrospectToken(context.Background(), c.token) - pkg.AssertError(t, c.expectErr, err, "TestIntrospect case", n, k) - if err == nil && c.assert != nil { - c.assert(ctx) - } - } - } -} - func TestAllowed(t *testing.T) { for n, w := range wardens { for k, c := range []struct { @@ -333,7 +298,7 @@ func TestTokenValid(t *testing.T) { expectErr: true, }, } { - ctx, err := w.InspectToken(context.Background(), c.token, c.scopes...) + ctx, err := w.TokenValid(context.Background(), c.token, c.scopes...) pkg.AssertError(t, c.expectErr, err, "ActionAllowed case", n, k) if err == nil && c.assert != nil { c.assert(ctx)