doorkeeper-openid-connect-1.1.2/0000755000175600017570000000000013065734330015622 5ustar pravipravidoorkeeper-openid-connect-1.1.2/LICENSE.txt0000644000175600017570000000205713065734330017451 0ustar pravipraviMIT License Copyright (c) 2014 PlayOn! Sports Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. doorkeeper-openid-connect-1.1.2/CONTRIBUTING.md0000644000175600017570000000472413065734330020062 0ustar pravipravi# Contributing ## Workflow We are using the [Feature Branch Workflow (also known as GitHub Flow)](https://guides.github.com/introduction/flow/), and prefer delivery as pull requests. Our first line of defense is the [Travis CI](https://travis-ci.org/doorkeeper-gem/doorkeeper-openid_connect) build defined within [.travis.yml](.travis.yml) and triggered for every pull request. Create a feature branch: ```sh git checkout -B feat/contributing ``` ## Creating Good Commits The cardinal rule for creating good commits is to ensure there is only one "logical change" per commit. Why is this an important rule? * The smaller the amount of code being changed, the quicker & easier it is to review & identify potential flaws. * If a change is found to be flawed later, it may be necessary to revert the broken commit. This is much easier to do if there are not other unrelated code changes entangled with the original commit. * When troubleshooting problems using Git's bisect capability, small well defined changes will aid in isolating exactly where the code problem was introduced. * When browsing history using Git annotate/blame, small well defined changes also aid in isolating exactly where & why a piece of code came from. Things to avoid when creating commits: * Mixing whitespace changes with functional code changes. * Mixing two unrelated functional changes. * Sending large new features in a single giant commit. ## Commit Message Conventions We use commit messages as per [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog): ```none (): ``` Allowed types: * **feat**: A new feature * **fix**: A bug fix * **docs**: Documentation only changes * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, newline, line endings, etc) * **refactor**: A code change that neither fixes a bug or adds a feature * **perf**: A code change that improves performance * **test**: Adding missing tests * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation You can add additional details after a new line to describe the change in detail or automatically close an issue on GitHub. ```none feat: create initial CONTRIBUTING.md This closes #73 ``` ## Release process - Bump version in `lib/doorkeeper/openid_connect/version.rb` - Update `CHANGELOG.md` - Commit all changes - Tag release and publish gem with `rake release` doorkeeper-openid-connect-1.1.2/.travis.yml0000644000175600017570000000043113065734330017731 0ustar pravipravisudo: false language: ruby cache: bundler before_install: - gem update bundler before_script: - bundle update script: - bundle exec rake spec env: - rails=4.2.0 - rails=5.0.0 rvm: - 2.1 - 2.2.5 - 2.3.1 matrix: exclude: - env: rails=5.0.0 rvm: 2.1 doorkeeper-openid-connect-1.1.2/bin/0000755000175600017570000000000013065734330016372 5ustar pravipravidoorkeeper-openid-connect-1.1.2/bin/setup0000755000175600017570000000020313065734330017453 0ustar pravipravi#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here doorkeeper-openid-connect-1.1.2/bin/console0000755000175600017570000000020413065734330017756 0ustar pravipravi#!/usr/bin/env ruby require 'bundler/setup' Bundler.require :default require 'doorkeeper/openid_connect' require 'pry' Pry.start doorkeeper-openid-connect-1.1.2/config/0000755000175600017570000000000013065734330017067 5ustar pravipravidoorkeeper-openid-connect-1.1.2/config/locales/0000755000175600017570000000000013065734330020511 5ustar pravipravidoorkeeper-openid-connect-1.1.2/config/locales/en.yml0000644000175600017570000000247313065734330021644 0ustar pravipravien: doorkeeper: scopes: openid: 'Authenticate your account' profile: 'View your profile information' email: 'View your email address' address: 'View your physical address' phone: 'View your phone number' errors: messages: login_required: 'The authorization server requires end-user authentication' consent_required: 'The authorization server requires end-user consent' interaction_required: 'The authorization server requires end-user interaction' account_selection_required: 'The authorization server requires end-user account selection' openid_connect: errors: messages: # Configuration error messages resource_owner_from_access_token_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.resource_owner_from_access_token missing configuration.' auth_time_from_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.auth_time_from_resource_owner missing configuration.' reauthenticate_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.reauthenticate_resource_owner missing configuration.' subject_not_configured: 'ID Token generation failed due to Doorkeeper::OpenidConnect.configure.subject missing configuration.' doorkeeper-openid-connect-1.1.2/doorkeeper-openid_connect.gemspec0000644000175600017570000000234113065734330024313 0ustar pravipravi$:.push File.expand_path('../lib', __FILE__) require 'doorkeeper/openid_connect/version' Gem::Specification.new do |spec| spec.name = 'doorkeeper-openid_connect' spec.version = Doorkeeper::OpenidConnect::VERSION spec.authors = ['Sam Dengler', 'Markus Koller'] spec.email = ['sam.dengler@playonsports.com', 'markus-koller@gmx.ch'] spec.homepage = 'https://github.com/doorkeeper-gem/doorkeeper-openid_connect' spec.summary = %q{OpenID Connect extension for Doorkeeper.} spec.description = %q{OpenID Connect extension for Doorkeeper.} spec.license = %q{MIT} spec.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features)/}) end spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.required_ruby_version = ">= 2.1" spec.add_runtime_dependency 'doorkeeper', '~> 4.0' spec.add_runtime_dependency 'json-jwt', '~> 1.6' spec.add_development_dependency 'rspec-rails' spec.add_development_dependency 'factory_girl' spec.add_development_dependency 'sqlite3' spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'conventional-changelog', '~> 1.2' end doorkeeper-openid-connect-1.1.2/README.md0000644000175600017570000002477713065734330017122 0ustar pravipravi# Doorkeeper::OpenidConnect [![Build Status](https://travis-ci.org/doorkeeper-gem/doorkeeper-openid_connect.svg?branch=master)](https://travis-ci.org/doorkeeper-gem/doorkeeper-openid_connect) [![Dependency Status](https://gemnasium.com/doorkeeper-gem/doorkeeper-openid_connect.svg?travis)](https://gemnasium.com/doorkeeper-gem/doorkeeper-openid_connect) [![Code Climate](https://codeclimate.com/github/doorkeeper-gem/doorkeeper-openid_connect.svg)](https://codeclimate.com/github/doorkeeper-gem/doorkeeper-openid_connect) [![Gem Version](https://badge.fury.io/rb/doorkeeper-openid_connect.svg)](https://rubygems.org/gems/doorkeeper-openid_connect) This library implements an [OpenID Connect](http://openid.net/connect/) authentication provider for Rails applications on top of the [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) OAuth 2.0 framework. OpenID Connect is a single-sign-on and identity layer with a [growing list of server and client implementations](http://openid.net/developers/libraries/). If you're looking for a client in Ruby check out [omniauth-openid-connect](https://github.com/jjbohn/omniauth-openid-connect/). ## Table of Contents - [Status](#status) - [Installation](#installation) - [Configuration](#configuration) - [Scopes](#scopes) - [Claims](#claims) - [Routes](#routes) - [Nonces](#nonces) - [Internationalization (I18n)](#internationalization-i18n) - [Development](#development) - [License](#license) - [Sponsors](#sponsors) ## Status The following parts of [OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) are currently supported: - [Authentication using the Authorization Code Flow](http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) - [Requesting Claims using Scope Values](http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) - [UserInfo Endpoint](http://openid.net/specs/openid-connect-core-1_0.html#UserInfo) - [Normal Claims](http://openid.net/specs/openid-connect-core-1_0.html#NormalClaims) In addition we also support most of [OpenID Connect Discovery 1.0](http://openid.net/specs/openid-connect-discovery-1_0.html) for automatic configuration discovery. Take a look at the [DiscoveryController](app/controllers/doorkeeper/openid_connect/discovery_controller.rb) for more details on supported features. ## Installation Make sure your application is already set up with [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper#installation). Add this line to your application's `Gemfile` and run `bundle install`: ```ruby gem 'doorkeeper-openid_connect' ``` Run the installation generator to update routes and create the initializer: ```sh rails generate doorkeeper:openid_connect:install ``` Generate a migration for Active Record (other ORMs are currently not supported): ```sh rails generate doorkeeper:openid_connect:migration rake db:migrate ``` If you're upgrading from an earlier version, check [CHANGELOG.md](CHANGELOG.md) for upgrade instructions. ## Configuration Make sure you've [configured Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper#configuration) before continuing. Verify your settings in `config/initializers/doorkeeper.rb`: - `resource_owner_authenticator` - This callback needs to returns a falsey value if the current user can't be determined: ```ruby resource_owner_authenticator do if current_user current_user else redirect_to(new_user_session_url) nil end end ``` The following settings are required in `config/initializers/doorkeeper_openid_connect.rb`: - `issuer` - Identifier for the issuer of the response (i.e. your application URL). The value is a case sensitive URL using the `https` scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components. - `subject` - Identifier for the resource owner (i.e. the authenticated user). A locally unique and never reassigned identifier within the issuer for the end-user, which is intended to be consumed by the client. The value is a case-sensitive string and must not exceed 255 ASCII characters in length. - The database ID of the user is an acceptable choice if you don't mind leaking that information. - `jws_private_key` - Private RSA key for [JSON Web Signature](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31). - You can generate a private key with the `openssl` command, see e.g. [Generate a keypair using OpenSSL](https://en.wikibooks.org/wiki/Cryptography/Generate_a_keypair_using_OpenSSL). - You should not commit the key to your repository, but use an external file (in combination with `File.read`) and/or the [dotenv-rails](https://github.com/bkeepers/dotenv) gem (in combination with `ENV[...]`). - `resource_owner_from_access_token` - Defines how to translate the Doorkeeper access token to a resource owner model. The following settings are optional, but recommended for better client compatibility: - `auth_time_from_resource_owner` - Returns the time of the user's last login, this can be a `Time`, `DateTime`, or any other class that responds to `to_i` - Required to support the `max_age` parameter and the `auth_time` claim. - `reauthenticate_resource_owner` - Defines how to trigger reauthentication for the current user (e.g. display a password prompt, or sign-out the user and redirect to the login form). - Required to support the `max_age` and `prompt=login` parameters. - The block is executed in the controller's scope, so you have access to methods like `params`, `redirect_to` etc. The following settings are optional: - `expiration` - Expiration time after which the ID Token must not be accepted for processing by clients. - The default is 120 seconds - `protocol` - The protocol to use when generating URIs for the discovery endpoints. - The default is `https` for production, and `http` for all other environments - Note that the OIC specification mandates HTTPS, so you shouldn't change this for production environments unless you have a really good reason! ### Scopes To perform authentication over OpenID Connect, an OAuth client needs to request the `openid` scope. This scope needs to be enabled using either `optional_scopes` in the global Doorkeeper configuration in `config/initializers/doorkeeper.rb`, or by adding it to any OAuth application's `scope` attribute. > Note that any application defining its own scopes won't inherit the scopes defined in the initializer, so you might have to update existing applications as well. > > See [Using Scopes](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes) in the Doorkeeper wiki for more information. ### Claims Claims can be defined in a `claims` block inside `config/initializers/doorkeeper_openid_connect.rb`: ```ruby Doorkeeper::OpenidConnect.configure do claims do claim :email do |resource_owner| resource_owner.email end claim :full_name do |resource_owner| "#{resource_owner.first_name} #{resource_owner.last_name}" end claim :preferred_username, scope: :openid do |resource_owner, application_scopes| # Pass the resource_owner's preferred_username if the application has # `profile` scope access. Otherwise, provide a more generic alternative. application_scopes.exists?(:profile) ? resource_owner.preferred_username : "summer-sun-9449" end end end ``` You can pass a `scope:` keyword argument on each claim to specify which OAuth scope should be required to access the claim. If you define any of the defined [Standard Claims](http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) they will by default use their [corresponding scopes](http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) (`profile`, `email`, `address` and `phone`), and any other claims will by default use the `profile` scope. Again, to use any of these scopes you need to enable them as described above. ### Routes The installation generator will update your `config/routes.rb` to define all required routes: ``` ruby Rails.application.routes.draw do use_doorkeeper_openid_connect # your routes end ``` This will mount the following routes: ``` GET /oauth/userinfo POST /oauth/userinfo GET /oauth/discovery/keys GET /.well-known/openid-configuration GET /.well-known/webfinger ``` With the exception of the hard-coded `/.well-known` paths (see [RFC 5785](https://tools.ietf.org/html/rfc5785)) you can customize routes in the same way as with Doorkeeper, please refer to [this page on their wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-routes#version--05-1). ### Nonces To support clients who send nonces you have to tweak Doorkeeper's authorization view so the parameter is passed on. If you don't already have custom templates, run this generator in your Rails application to add them: ```sh rails generate doorkeeper:views ``` Then tweak the template as follows: ```patch --- i/app/views/doorkeeper/authorizations/new.html.erb +++ w/app/views/doorkeeper/authorizations/new.html.erb @@ -26,6 +26,7 @@ <%= hidden_field_tag :state, @pre_auth.state %> <%= hidden_field_tag :response_type, @pre_auth.response_type %> <%= hidden_field_tag :scope, @pre_auth.scope %> + <%= hidden_field_tag :nonce, @pre_auth.nonce %> <%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %> <% end %> <%= form_tag oauth_authorization_path, method: :delete do %> @@ -34,6 +35,7 @@ <%= hidden_field_tag :state, @pre_auth.state %> <%= hidden_field_tag :response_type, @pre_auth.response_type %> <%= hidden_field_tag :scope, @pre_auth.scope %> + <%= hidden_field_tag :nonce, @pre_auth.nonce %> <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %> <% end %> ``` ### Internationalization (I18n) We use Rails locale files for error messages and scope descriptions, see [config/locales/en.yml](config/locales/en.yml). You can override these by adding them to your own translations in `config/locale`. ## Development Run `bundle install` to setup all development dependencies. To run all specs: ```sh bundle exec rspec ``` To run the local engine server: ```sh cd spec/dummy bundle exec rails server ``` By default, the latest Rails version is used. To use a specific version run: ``` rails=4.2.0 bundle update ``` ## License Doorkeeper::OpenidConnect is released under the [MIT License](http://www.opensource.org/licenses/MIT). ## Sponsors Initial development of this project was sponsored by [PlayOn! Sports](https://github.com/playon). doorkeeper-openid-connect-1.1.2/CHANGELOG.md0000644000175600017570000000435213065734330017437 0ustar pravipravi### Unreleased #### Changes ### v1.1.2 (2017-01-18) ### Bugfixes - Fixes the `undefined local variable or method 'pre_auth'` error ### v1.1.1 (2017-01-18) #### Upgrading - The configuration setting `jws_public_key` wasn't actually used, it's deprecated now and will be removed in the next major release - The undocumented shorthand `to_proc` syntax for defining claims (`claim :user, &:name`) is not supported anymore #### Features - Claims now receive an optional second `scopes` argument which allow you to dynamically adjust claim values based on the requesting applications' scopes (by @nbibler) - The `prompt` parameter values `login` and `consent` are now supported - The configuration setting `protocol` was added (by @gigr) #### Bugfixes - Standard Claims are now mapped correctly to their default scopes (by @tylerhunt) - Blank `nonce` parameters are now ignored #### Changes - `nil` values and empty strings are now removed from the UserInfo and IdToken responses - Allow `json-jwt` dependency at ~> 1.6. (by @nbibler) - Configuration blocks no longer internally use `instance_eval` which previously gave undocumented and unexpected `self` access to the caller (by @nbibler) ### v1.1.0 (2016-11-30) This release is a general clean-up and adds support for some advanced OpenID Connect features. #### Upgrading - This version adds a table to store temporary nonces, use the generator `doorkeeper:openid_connect:migration` to create a migration - Implement the new configuration callbacks `auth_time_from_resource_owner` and `reauthenticate_resource_owner` to support advanced features #### Features - Add discovery endpoint ([a16caa8](/../../commit/a16caa8)) - Add webfinger and keys endpoints for discovery ([f70898b](/../../commit/f70898b)) - Add supported claims to discovery response ([1d8f9ea](/../../commit/1d8f9ea)) - Support prompt=none parameter ([c775d8b](/../../commit/c775d8b)) - Store and return nonces in IdToken responses ([d28ca8c](/../../commit/d28ca8c)) - Add generator for initializer ([80399fd](/../../commit/80399fd)) - Support max_age parameter ([aabe3aa](/../../commit/aabe3aa)) - Respect scope grants in UserInfo response ([25f2170](/../../commit/25f2170)) doorkeeper-openid-connect-1.1.2/app/0000755000175600017570000000000013065734330016402 5ustar pravipravidoorkeeper-openid-connect-1.1.2/app/controllers/0000755000175600017570000000000013065734330020750 5ustar pravipravidoorkeeper-openid-connect-1.1.2/app/controllers/doorkeeper/0000755000175600017570000000000013065734330023107 5ustar pravipravidoorkeeper-openid-connect-1.1.2/app/controllers/doorkeeper/openid_connect/0000755000175600017570000000000013065734330026076 5ustar pravipravidoorkeeper-openid-connect-1.1.2/app/controllers/doorkeeper/openid_connect/discovery_controller.rb0000644000175600017570000000520213065734330032674 0ustar pravipravimodule Doorkeeper module OpenidConnect class DiscoveryController < ::Doorkeeper::ApplicationController include Doorkeeper::Helpers::Controller WEBFINGER_RELATION = 'http://openid.net/specs/connect/1.0/issuer'.freeze def provider render json: provider_response end def webfinger render json: webfinger_response end def keys render json: keys_response end private def provider_response doorkeeper = ::Doorkeeper.configuration openid_connect = ::Doorkeeper::OpenidConnect.configuration { issuer: openid_connect.issuer, authorization_endpoint: oauth_authorization_url(protocol: protocol), token_endpoint: oauth_token_url(protocol: protocol), userinfo_endpoint: oauth_userinfo_url(protocol: protocol), jwks_uri: oauth_discovery_keys_url(protocol: protocol), scopes_supported: doorkeeper.scopes, # TODO: support id_token response type response_types_supported: doorkeeper.authorization_response_types, response_modes_supported: [ 'query', 'fragment' ], token_endpoint_auth_methods_supported: [ 'client_secret_basic', 'client_secret_post', # TODO: look into doorkeeper-jwt_assertion for these #'client_secret_jwt', #'private_key_jwt' ], # TODO: make this configurable subject_types_supported: [ 'public', ], # TODO: make this configurable id_token_signing_alg_values_supported: [ 'RS256', ], claim_types_supported: [ 'normal', # TODO: support these #'aggregated', #'distributed', ], claims_supported: [ 'iss', 'sub', 'aud', 'exp', 'iat', ] | openid_connect.claims.to_h.keys, } end def webfinger_response { subject: params.require(:resource), links: [ { rel: WEBFINGER_RELATION, href: root_url(protocol: protocol), } ] } end def keys_response signing_key = Doorkeeper::OpenidConnect.signing_key { keys: [ signing_key.slice(:kty, :kid, :e, :n).merge( use: 'sig', alg: Doorkeeper::OpenidConnect::SIGNING_ALGORITHM ) ] } end def protocol Doorkeeper::OpenidConnect.configuration.protocol.call end end end end doorkeeper-openid-connect-1.1.2/app/controllers/doorkeeper/openid_connect/userinfo_controller.rb0000644000175600017570000000101713065734330032517 0ustar pravipravimodule Doorkeeper module OpenidConnect class UserinfoController < ::Doorkeeper::ApplicationController skip_before_action :verify_authenticity_token before_action -> { doorkeeper_authorize! :openid } def show resource_owner = Doorkeeper::OpenidConnect.configuration.resource_owner_from_access_token.call(doorkeeper_token) user_info = Doorkeeper::OpenidConnect::UserInfo.new(resource_owner, doorkeeper_token.scopes) render json: user_info, status: :ok end end end end doorkeeper-openid-connect-1.1.2/Gemfile0000644000175600017570000000022013065734330017107 0ustar pravipravisource 'https://rubygems.org' # use Rails version specified by environment ENV['rails'] ||= '5.0.0' gem 'rails', "~> #{ENV['rails']}" gemspec doorkeeper-openid-connect-1.1.2/.ruby-version0000644000175600017570000000000613065734330020263 0ustar pravipravi2.3.3 doorkeeper-openid-connect-1.1.2/lib/0000755000175600017570000000000013065734330016370 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/generators/0000755000175600017570000000000013065734330020541 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/0000755000175600017570000000000013065734330022700 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/0000755000175600017570000000000013065734330025667 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/install_generator.rb0000644000175600017570000000106613065734330031733 0ustar pravipravimodule Doorkeeper module OpenidConnect class InstallGenerator < ::Rails::Generators::Base include ::Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) desc 'Installs Doorkeeper OpenID Connect.' def install template 'initializer.rb', 'config/initializers/doorkeeper_openid_connect.rb' copy_file File.expand_path('../../../../../config/locales/en.yml', __FILE__), 'config/locales/doorkeeper_openid_connect.en.yml' route 'use_doorkeeper_openid_connect' end end end end doorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/migration_generator.rb0000644000175600017570000000110713065734330032252 0ustar pravipravirequire 'rails/generators/active_record' module Doorkeeper module OpenidConnect class MigrationGenerator < ::Rails::Generators::Base include ::Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) desc 'Installs Doorkeeper OpenID Connect migration file.' def install migration_template 'migration.rb', 'db/migrate/create_doorkeeper_openid_connect_tables.rb' end def self.next_migration_number(dirname) ActiveRecord::Generators::Base.next_migration_number(dirname) end end end end doorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/templates/0000755000175600017570000000000013065734330027665 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/templates/migration.rb0000644000175600017570000000053013065734330032201 0ustar pravipraviclass CreateDoorkeeperOpenidConnectTables < ActiveRecord::Migration def change create_table :oauth_openid_requests do |t| t.integer :access_grant_id, null: false t.string :nonce, null: false end add_foreign_key( :oauth_openid_requests, :oauth_access_grants, column: :access_grant_id ) end end doorkeeper-openid-connect-1.1.2/lib/generators/doorkeeper/openid_connect/templates/initializer.rb0000644000175600017570000000234413065734330032540 0ustar pravipraviDoorkeeper::OpenidConnect.configure do issuer 'issuer string' jws_private_key <<-EOL -----BEGIN RSA PRIVATE KEY----- .... -----END RSA PRIVATE KEY----- EOL resource_owner_from_access_token do |access_token| # Example implementation: # User.find_by(id: access_token.resource_owner_id) end auth_time_from_resource_owner do |resource_owner| # Example implementation: # resource_owner.current_sign_in_at end reauthenticate_resource_owner do |resource_owner, return_to| # Example implementation: # store_location_for resource_owner, return_to # sign_out resource_owner # redirect_to new_user_session_url end subject do |resource_owner| # Example implementation: # resource_owner.key end # Protocol to use when generating URIs for the discovery endpoint, # for example if you also use HTTPS in development # protocol do # :https # end # Expiration time on or after which the ID Token MUST NOT be accepted for processing. (default 120 seconds). # expiration 600 # Example claims: # claims do # normal_claim :_foo_ do |resource_owner| # resource_owner.foo # end # normal_claim :_bar_ do |resource_owner| # resource_owner.bar # end # end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/0000755000175600017570000000000013065734330020527 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/0000755000175600017570000000000013065734330023516 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/errors.rb0000644000175600017570000000174313065734330025364 0ustar pravipravimodule Doorkeeper module OpenidConnect module Errors class OpenidConnectError < StandardError def error_name self.class.name.demodulize.underscore end end # internal errors class InvalidConfiguration < OpenidConnectError; end class MissingConfiguration < OpenidConnectError def initialize super('Configuration for Doorkeeper OpenID Connect missing. Do you have doorkeeper_openid_connect initializer?') end end # OAuth 2.0 errors # https://tools.ietf.org/html/rfc6749#section-4.1.2.1 class InvalidRequest < OpenidConnectError; end # OpenID Connect 1.0 errors # http://openid.net/specs/openid-connect-core-1_0.html#AuthError class LoginRequired < OpenidConnectError; end class ConsentRequired < OpenidConnectError; end class InteractionRequired < OpenidConnectError; end class AccountSelectionRequired < OpenidConnectError; end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims_builder.rb0000644000175600017570000000076213065734330027026 0ustar pravipravirequire 'ostruct' module Doorkeeper module OpenidConnect class ClaimsBuilder def initialize(&block) @claims = OpenStruct.new instance_eval(&block) end def build @claims end def normal_claim(name, scope: nil, &block) @claims[name] = Claims::NormalClaim.new( name: name, scope: scope, generator: block ) end alias_method :claim, :normal_claim end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/user_info.rb0000644000175600017570000000156513065734330026043 0ustar pravipravimodule Doorkeeper module OpenidConnect class UserInfo include ActiveModel::Validations def initialize(resource_owner, scopes) @resource_owner = resource_owner @scopes = scopes end def claims base_claims.merge resource_owner_claims end def as_json(*_) claims.reject { |_, value| value.nil? || value == '' } end private def base_claims { sub: subject } end def resource_owner_claims Doorkeeper::OpenidConnect.configuration.claims.to_h.map do |name, claim| if @scopes.exists? claim.scope [name, claim.generator.call(@resource_owner, @scopes)] end end.compact.to_h end def subject Doorkeeper::OpenidConnect.configuration.subject.call(@resource_owner).to_s end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/orm/0000755000175600017570000000000013065734330024313 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/orm/active_record/0000755000175600017570000000000013065734330027124 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/orm/active_record/request.rb0000644000175600017570000000055613065734330031147 0ustar pravipravimodule Doorkeeper module OpenidConnect class Request < ActiveRecord::Base self.table_name = "#{table_name_prefix}oauth_openid_requests#{table_name_suffix}".to_sym validates :access_grant_id, :nonce, presence: true belongs_to :access_grant, class_name: 'Doorkeeper::AccessGrant', inverse_of: :openid_request end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/orm/active_record/access_grant.rb0000644000175600017570000000060213065734330032103 0ustar pravipravimodule Doorkeeper module OpenidConnect module AccessGrant def self.prepended(base) base.class_eval do has_one :openid_request, class_name: 'Doorkeeper::OpenidConnect::Request', inverse_of: :access_grant, dependent: :delete end end end end AccessGrant.send :prepend, OpenidConnect::AccessGrant end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/orm/active_record.rb0000644000175600017570000000126413065734330027454 0ustar pravipravimodule Doorkeeper module OpenidConnect module Orm module ActiveRecord def initialize_models! super require 'doorkeeper/openid_connect/orm/active_record/access_grant' require 'doorkeeper/openid_connect/orm/active_record/request' if Doorkeeper.configuration.active_record_options[:establish_connection] [Doorkeeper::OpenidConnect::Request].each do |c| c.send :establish_connection, Doorkeeper.configuration.active_record_options[:establish_connection] end end end end end end Orm::ActiveRecord.singleton_class.send :prepend, OpenidConnect::Orm::ActiveRecord end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/version.rb0000644000175600017570000000012013065734330025521 0ustar pravipravimodule Doorkeeper module OpenidConnect VERSION = '1.1.2'.freeze end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims/0000755000175600017570000000000013065734330024766 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims/distributed_claim.rb0000644000175600017570000000025313065734330031002 0ustar pravipravimodule Doorkeeper module OpenidConnect module Claims class DistributedClaim < Claim attr_accessor :endpoint, :access_token end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims/claim.rb0000644000175600017570000000206013065734330026376 0ustar pravipravimodule Doorkeeper module OpenidConnect module Claims class Claim attr_accessor :name, :scope # http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims # http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims STANDARD_CLAIMS = { profile: %i[ name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at ], email: %i[ email email_verified ], address: %i[ address ], phone: %i[ phone_number phone_number_verified ], } def initialize(options = {}) @name = options[:name].to_sym @scope = options[:scope].to_sym if options[:scope] # use default scope for Standard Claims @scope ||= STANDARD_CLAIMS.find do |_scope, claims| claims.include? @name end.try(:first) # use profile scope as default fallback @scope ||= :profile end end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims/normal_claim.rb0000644000175600017570000000047413065734330027755 0ustar pravipravimodule Doorkeeper module OpenidConnect module Claims class NormalClaim < Claim attr_reader :generator def initialize(options = {}) super(options) @generator = options[:generator] end def type :normal end end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/claims/aggregated_claim.rb0000644000175600017570000000022613065734330030552 0ustar pravipravimodule Doorkeeper module OpenidConnect module Claims class AggregatedClaim < Claim attr_accessor :jwt end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/id_token.rb0000644000175600017570000000264213065734330025643 0ustar pravipravimodule Doorkeeper module OpenidConnect class IdToken include ActiveModel::Validations attr_reader :nonce def initialize(access_token, nonce = nil) @access_token = access_token @nonce = nonce @resource_owner = Doorkeeper::OpenidConnect.configuration.resource_owner_from_access_token.call(access_token) @issued_at = Time.now end def claims { iss: issuer, sub: subject, aud: audience, exp: expiration, iat: issued_at, nonce: nonce, auth_time: auth_time, } end def as_json(*_) claims.reject { |_, value| value.nil? || value == '' } end def as_jws_token JSON::JWT.new(as_json).sign(Doorkeeper::OpenidConnect.signing_key).to_s end private def issuer Doorkeeper::OpenidConnect.configuration.issuer end def subject Doorkeeper::OpenidConnect.configuration.subject.call(@resource_owner).to_s end def audience @access_token.application.uid end def expiration (@issued_at.utc + Doorkeeper::OpenidConnect.configuration.expiration).to_i end def issued_at @issued_at.utc.to_i end def auth_time Doorkeeper::OpenidConnect.configuration.auth_time_from_resource_owner.call(@resource_owner).try(:to_i) end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/0000755000175600017570000000000013065734330024636 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/authorization/0000755000175600017570000000000013065734330027536 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/authorization/code.rb0000644000175600017570000000105113065734330030772 0ustar pravipravimodule Doorkeeper module OpenidConnect module OAuth module Authorization module Code def issue_token super.tap do |access_grant| if pre_auth.nonce.present? ::Doorkeeper::OpenidConnect::Request.create!( access_grant: access_grant, nonce: pre_auth.nonce ) end end end end end end end OAuth::Authorization::Code.send :prepend, OpenidConnect::OAuth::Authorization::Code end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/password_access_token_request.rb0000644000175600017570000000112613065734330033316 0ustar pravipravimodule Doorkeeper module OpenidConnect module OAuth module PasswordAccessTokenRequest attr_reader :nonce def initialize(server, client, resource_owner, parameters = {}) super @nonce = parameters[:nonce] end private def after_successful_response super id_token = Doorkeeper::OpenidConnect::IdToken.new(access_token, nonce) @response.id_token = id_token end end end end OAuth::PasswordAccessTokenRequest.send :prepend, OpenidConnect::OAuth::PasswordAccessTokenRequest end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/token_response.rb0000644000175600017570000000072513065734330030225 0ustar pravipravimodule Doorkeeper module OpenidConnect module OAuth module TokenResponse attr_accessor :id_token def body if token.includes_scope? 'openid' super .merge(id_token: id_token.try(:as_jws_token)) .reject { |_, value| value.blank? } else super end end end end end OAuth::TokenResponse.send :prepend, OpenidConnect::OAuth::TokenResponse end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/pre_authorization.rb0000644000175600017570000000052113065734330030727 0ustar pravipravimodule Doorkeeper module OpenidConnect module OAuth module PreAuthorization attr_reader :nonce def initialize(server, client, attrs = {}) super @nonce = attrs[:nonce] end end end end OAuth::PreAuthorization.send :prepend, OpenidConnect::OAuth::PreAuthorization end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/oauth/authorization_code_request.rb0000644000175600017570000000111313065734330032621 0ustar pravipravimodule Doorkeeper module OpenidConnect module OAuth module AuthorizationCodeRequest private def after_successful_response super nonce = if openid_request = grant.openid_request openid_request.destroy! openid_request.nonce end id_token = Doorkeeper::OpenidConnect::IdToken.new(access_token, nonce) @response.id_token = id_token end end end end OAuth::AuthorizationCodeRequest.send :prepend, OpenidConnect::OAuth::AuthorizationCodeRequest end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/helpers/0000755000175600017570000000000013065734330025160 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/helpers/controller.rb0000644000175600017570000000632713065734330027700 0ustar pravipravimodule Doorkeeper module OpenidConnect module Helpers module Controller private def authenticate_resource_owner! super.tap do |owner| next unless respond_to?(:pre_auth, true) next unless pre_auth.scopes.include? 'openid' handle_prompt_param!(owner) handle_max_age_param!(owner) end rescue Errors::OpenidConnectError => exception # clear the previous response body to avoid a DoubleRenderError self.response_body = nil # FIXME: workaround for Rails 5, see https://github.com/rails/rails/issues/25106 @_response_body = nil error = ::Doorkeeper::OAuth::ErrorResponse.new(name: exception.error_name) response.headers.merge!(error.headers) render json: error.body, status: error.status end def handle_prompt_param!(owner) prompt_values ||= params[:prompt].to_s.split(/ +/).uniq prompt_values.each do |prompt| case prompt when 'none' then raise Errors::InvalidRequest if (prompt_values - [ 'none' ]).any? raise Errors::LoginRequired unless owner raise Errors::ConsentRequired unless matching_tokens_for_resource_owner(owner).present? when 'login' then reauthenticate_resource_owner(owner) if owner when 'consent' then matching_tokens_for_resource_owner(owner).map(&:destroy) when 'select_account' then # TODO: let the user implement this raise Errors::AccountSelectionRequired else raise Errors::InvalidRequest end end end def handle_max_age_param!(owner) max_age = params[:max_age].to_i return unless max_age > 0 && owner auth_time = instance_exec owner, &Doorkeeper::OpenidConnect.configuration.auth_time_from_resource_owner if !auth_time || (Time.zone.now - auth_time) > max_age reauthenticate_resource_owner(owner) end end def reauthenticate_resource_owner(owner) return_to = URI.parse(request.path) return_to.query = request.query_parameters.tap do |params| params['prompt'] = params['prompt'].to_s.sub(/\blogin\s*\b/, '').strip params.delete('prompt') if params['prompt'].blank? end.to_query instance_exec owner, return_to.to_s, &Doorkeeper::OpenidConnect.configuration.reauthenticate_resource_owner raise Errors::LoginRequired unless performed? end def matching_tokens_for_resource_owner(owner) # TODO: maybe use Doorkeeper::AccessToken.matching_token_for once # https://github.com/doorkeeper-gem/doorkeeper/pull/907 is merged Doorkeeper::AccessToken.where( application_id: pre_auth.client.id, resource_owner_id: owner.id, revoked_at: nil, ).select do |token| Doorkeeper::AccessToken.scopes_match?(token.scopes, pre_auth.scopes, nil) end end end end end Helpers::Controller.send :prepend, OpenidConnect::Helpers::Controller end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/config.rb0000644000175600017570000001007013065734330025306 0ustar pravipravimodule Doorkeeper module OpenidConnect def self.configure(&block) if Doorkeeper.configuration.orm != :active_record fail Errors::InvalidConfiguration, 'Doorkeeper OpenID Connect currently only supports the ActiveRecord ORM adapter' end @config = Config::Builder.new(&block).build end def self.configuration @config || (fail Errors::MissingConfiguration) end class Config class Builder def initialize(&block) @config = Config.new instance_eval(&block) end def build @config end def jws_public_key(*args) puts "DEPRECATION WARNING: `jws_public_key` is not needed anymore and will be removed in a future version, please remove it from config/initializers/doorkeeper_openid_connect.rb" end end module Option # Defines configuration option # # When you call option, it defines two methods. One method will take place # in the +Config+ class and the other method will take place in the # +Builder+ class. # # The +name+ parameter will set both builder method and config attribute. # If the +:as+ option is defined, the builder method will be the specified # option while the config attribute will be the +name+ parameter. # # If you want to introduce another level of config DSL you can # define +builder_class+ parameter. # Builder should take a block as the initializer parameter and respond to function +build+ # that returns the value of the config attribute. # # ==== Options # # * [:+as+] Set the builder method that goes inside +configure+ block # * [+:default+] The default value in case no option was set # # ==== Examples # # option :name # option :name, as: :set_name # option :name, default: 'My Name' # option :scopes builder_class: ScopesBuilder # def option(name, options = {}) attribute = options[:as] || name attribute_builder = options[:builder_class] Builder.instance_eval do define_method name do |*args, &block| # TODO: is builder_class option being used? value = if attribute_builder attribute_builder.new(&block).build else block ? block : args.first end @config.instance_variable_set(:"@#{attribute}", value) end end define_method attribute do |*_| if instance_variable_defined?(:"@#{attribute}") instance_variable_get(:"@#{attribute}") else options[:default] end end public attribute end def extended(base) base.send(:private, :option) end end extend Option option :jws_private_key option :issuer option :resource_owner_from_access_token, default: lambda { |*_| fail Errors::InvalidConfiguration, I18n.translate('doorkeeper.openid_connect.errors.messages.resource_owner_from_access_token_not_configured') } option :auth_time_from_resource_owner, default: lambda { |*_| fail Errors::InvalidConfiguration, I18n.translate('doorkeeper.openid_connect.errors.messages.auth_time_from_resource_owner_not_configured') } option :reauthenticate_resource_owner, default: lambda { |*_| fail Errors::InvalidConfiguration, I18n.translate('doorkeeper.openid_connect.errors.messages.reauthenticate_resource_owner_not_configured') } option :subject, default: lambda { |*_| fail Errors::InvalidConfiguration, I18n.translate('doorkeeper.openid_connect.errors.messages.subject_not_configured') } option :expiration, default: 120 option :claims, builder_class: ClaimsBuilder option :protocol, default: lambda { |*_| ::Rails.env.production? ? :https : :http } end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/engine.rb0000644000175600017570000000033213065734330025306 0ustar pravipravimodule Doorkeeper module OpenidConnect class Engine < ::Rails::Engine initializer 'doorkeeper.openid_connect.routes' do Doorkeeper::OpenidConnect::Rails::Routes.install! end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/rails/0000755000175600017570000000000013065734330024630 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/rails/routes/0000755000175600017570000000000013065734330026151 5ustar pravipravidoorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/rails/routes/mapping.rb0000644000175600017570000000137513065734330030137 0ustar pravipravimodule Doorkeeper module OpenidConnect module Rails class Routes class Mapping attr_accessor :controllers, :as, :skips def initialize @controllers = { userinfo: 'doorkeeper/openid_connect/userinfo', discovery: 'doorkeeper/openid_connect/discovery' } @as = { userinfo: :userinfo, discovery: :discovery } @skips = [] end def [](routes) { controllers: @controllers[routes], as: @as[routes] } end def skipped?(controller) @skips.include?(controller) end end end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/rails/routes/mapper.rb0000644000175600017570000000122713065734330027764 0ustar pravipravimodule Doorkeeper module OpenidConnect module Rails class Routes class Mapper def initialize(mapping = Mapping.new) @mapping = mapping end def map(&block) instance_eval(&block) if block @mapping end def controllers(controller_names = {}) @mapping.controllers.merge!(controller_names) end def skip_controllers(*controller_names) @mapping.skips = controller_names end def as(alias_names = {}) @mapping.as.merge!(alias_names) end end end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect/rails/routes.rb0000644000175600017570000000350213065734330026476 0ustar pravipravirequire 'doorkeeper/openid_connect/rails/routes/mapping' require 'doorkeeper/openid_connect/rails/routes/mapper' module Doorkeeper module OpenidConnect module Rails class Routes module Helper def use_doorkeeper_openid_connect(options = {}, &block) Doorkeeper::OpenidConnect::Rails::Routes.new(self, &block).generate_routes!(options) end end def self.install! ActionDispatch::Routing::Mapper.send :include, Doorkeeper::OpenidConnect::Rails::Routes::Helper end attr_accessor :routes def initialize(routes, &block) @routes = routes @block = block end def generate_routes!(options) @mapping = Mapper.new.map(&@block) routes.scope options[:scope] || 'oauth', as: 'oauth' do map_route(:userinfo, :userinfo_routes) map_route(:discovery, :discovery_routes) end routes.scope as: 'oauth' do map_route(:discovery, :discovery_well_known_routes) end end private def map_route(name, method) return if @mapping.skipped?(name) mapping = @mapping[name] routes.scope controller: mapping[:controllers], as: mapping[:as] do send method end end def userinfo_routes routes.get :show, path: 'userinfo', as: '' routes.post :show, path: 'userinfo', as: nil end def discovery_routes routes.scope path: 'discovery' do routes.get :keys end end def discovery_well_known_routes routes.scope path: '.well-known' do routes.get :provider, path: 'openid-configuration' routes.get :webfinger end end end end end end doorkeeper-openid-connect-1.1.2/lib/doorkeeper/openid_connect.rb0000644000175600017570000000225413065734330024046 0ustar pravipravirequire 'doorkeeper' require 'active_model' require 'json/jwt' require 'doorkeeper/openid_connect/claims_builder' require 'doorkeeper/openid_connect/claims/claim' require 'doorkeeper/openid_connect/claims/normal_claim' require 'doorkeeper/openid_connect/config' require 'doorkeeper/openid_connect/engine' require 'doorkeeper/openid_connect/errors' require 'doorkeeper/openid_connect/id_token' require 'doorkeeper/openid_connect/user_info' require 'doorkeeper/openid_connect/version' require 'doorkeeper/openid_connect/helpers/controller' require 'doorkeeper/openid_connect/oauth/authorization/code' require 'doorkeeper/openid_connect/oauth/authorization_code_request' require 'doorkeeper/openid_connect/oauth/password_access_token_request' require 'doorkeeper/openid_connect/oauth/pre_authorization' require 'doorkeeper/openid_connect/oauth/token_response' require 'doorkeeper/openid_connect/orm/active_record' require 'doorkeeper/openid_connect/rails/routes' module Doorkeeper module OpenidConnect # TODO: make this configurable SIGNING_ALGORITHM = 'RS256'.freeze def self.signing_key JSON::JWK.new(OpenSSL::PKey.read(configuration.jws_private_key)) end end end doorkeeper-openid-connect-1.1.2/Rakefile0000644000175600017570000000017413065734330017271 0ustar pravipravirequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new task default: :spec task test: :spec doorkeeper-openid-connect-1.1.2/.gitignore0000644000175600017570000000015613065734330017614 0ustar pravipravi/.bundle /Gemfile.lock /spec/dummy/db/*.sqlite3 /spec/dummy/log/*.log /spec/dummy/tmp/ /spec/examples.txt /pkg