Compare commits

..

No commits in common. "master" and "v1.4.0" have entirely different histories.

323 changed files with 16547 additions and 5563 deletions

6
.gitignore vendored
View file

@ -1 +1,5 @@
*~ nbproject/
.idea/
completer.hist
feed/
node_modules/

View file

@ -1,11 +0,0 @@
image: php:7.0
before_script:
- bash ci/docker_install.sh > /dev/null
stages:
- test
test:
script:
- phpunit tests/

View file

@ -4,38 +4,8 @@
- [B] = beta release - [B] = beta release
- [D] = development release - [D] = development release
- [S] release are always compared to the previous [S] release. - [S] release are always compared to the previous [S] release.
## Version 1.5.0 (2016-08-03) [S]
- **[FEATURE]** Ability to only show post excerpts on overview pages
- **[FIX]** Fixed twitter username not specified in connfig.yaml
- **[FIX]** Fixed wrong OpenGraph meta tags
- **[CODE]** Further improved code style
- **[CODE]** Added unit tests for Config.php and ArticleGenerator.php
- **[NOTE]** If you have own language file, please update them. See also the upgrade guide
## Version 1.4.4 (2016-06-03) [S]
- **[FIX]** Error when trying to create a feed
## Version 1.4.3 (2016-05-21) [S]
- **[FIX]** Missing space in drawer between "Blogs on" and blogname
- **[FIX]** Background layer was not removed if drawer was closed
- **[FEATURE]** nextDESIGN theme added to delivered themes
- **[NOTE]** The update script to 1.4.3 works with 1.4.0, 1.4.1 and 1.4.2
## Version 1.4.2 (2016-05-18) [S]
- [FIX]: Password verification not implemented in RCC login page
## Version 1.4.1 (2016-05-18) [S]
- Switch to HTTP Basic Auth for the RCC API
- Store password as hash
- Require SSL for using the RCC and the RCC API
- [FIX] Drawer "Go back" does not work
- [FIX] Missing nodejs dependencies
- [FIX] Disqus not completly migrated to new config
## Version 1.4.0 (2016-05-07) [S] ## Version 1.4.0 (2016-05-07) [S]
- Fix: Feeds contain no text - Fix: Feeds contain no text

View file

@ -1,20 +1,18 @@
# Rangitaki PHP blogging engine # Rangitaki PHP blogging engine
[![Join the chat at https://gitter.im/rangitaki/rangitaki](https://badges.gitter.im/canax/view.svg)](https://gitter.im/rangitaki/view?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Rangitaki is a simple to use and easy to configure blogging engine, written in PHP and it has absolutely no database dependencies. Rangitaki is a simple to use and easy to configure blogging engine, written in PHP and it has absolutely no database dependencies.
Tested with PHP version 5.5 until 7.0. Tested with PHP version 5.5 until 7.0.
![Rangitaki](https://gitlab.com/mmk2410/rangitaki/raw/master/feature-graphic.png) ![Rangitaki](https://marcel-kapfer.de/blog/media/with-name.png)
[Wiki](https://gitlab.com/mmk2410/rangitaki/wikis/home) [Website](https://marcel-kapfer.de/rangitaki)
[About](https://gitlab.com/mmk2410/rangitaki/wikis/about) [About](https://marcel-kapfer.de/rangitaki/about)
[Documentation](https://gitlab.com/mmk2410/rangitaki/wikis/docs) [Documentation](https://marcel-kapfer.de/rangitaki/docs)
[Quick Starting Guide](https://gitlab.com/mmk2410/rangitaki/wikis/docs/quickstart) [Quick Starting Guide](https://marcel-kapfer.de/rangitaki/docs/quick)
## What is it? ## What is it?
@ -40,14 +38,14 @@ My goal for Rangitaki was (and still is) to create a blogging engine without dat
- JavaScript Extension Support - JavaScript Extension Support
- Pagination support - Pagination support
- Atom feed generation - Atom feed generation
- Rangitaki Control Center (RCC; optional, read the [RCC Documentation](https://gitlab.com/mmk2410/rangitaki/wikis/docs/rcc) - Rangitaki Control Center (RCC; optional, read the [RCC Documentation](https://marcel-kapfer.de/rangitaki/docs/rcc))
- Have a look under 'What is that RCC?' in this readme - Have a look under 'What is that RCC?' in this readme
## Did you say 'themes'? ## Did you say 'themes'?
Yes. Rangitaki has a theme support which makes it easy to customize your blog concerning design. Yes. Rangitaki has a theme support which makes it easy to customize your blog concerning design.
[Read the theme guide](https://gitlab.com/mmk2410/rangitaki/wikis/docs/themes) [Read the theme guide](https://marcel-kapfer.de/rangitaki/docs/themes)
## What is that RCC? ## What is that RCC?
@ -60,7 +58,16 @@ It has the following features:
- Media upload - Media upload
- Atom feed generation - Atom feed generation
[Read the RCC documentation](https://gitlab.com/mmk2410/rangitaki/wikis/docs/rcc) [Read the RCC documentation](https://marcel-kapfer.de/rangitaki/docs/rcc)
## Where can I see an example?
- Official Rangitaki blog [marcel-kapfer.de/rangitaki/blog](https://marcel-kapfer.de/rangitaki/blog)
- My personal blog
[marcel-kapfer.de/blog](https://marcel-kapfer.de/blog)
Would you like to see your Rangitaki blog here? Write me a message at [marcelmichaelkapfer@yahoo.co.nz](mailto:marcelmichaelkapfer@yahoo.co.nz)
## Used Libraries ## Used Libraries
@ -71,7 +78,8 @@ It has the following features:
## Issues, Requests, etc. ## Issues, Requests, etc.
For bug reports, feature requests and all other questions or recommendations feel free to create an issue here at [GitLab](https://gitlab.com/mmk2410/rangitaki/issues). For bug reports, feature requests and all other stuff use
[phab.mmk2410.org/maniphest](https://phab.mmk2410.org/maniphest).
## Code ## Code
@ -79,13 +87,13 @@ For bug reports, feature requests and all other questions or recommendations fee
2. Create your feature branch (`git checkout -b my-new-feature`) 2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`) 3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`) 4. Push to the branch (`git push origin my-new-feature`)
5. Create new merge request 5. Create New Pull Request
Read also the [contributing documentation](https://gitlab.com/mmk2410/rangitaki/wikis/docs/contribute) Read also the [contributing documentation](https://marcel-kapfer.de/rangitaki/docs/contrib)
## Social ## Social
You can follow me on Twitter or subscribe my blog to receive news about Rangitaki. You can follow my personal Twitter and Google+ account to receive news about the Rangitaki blogging engine.
- [Twitter @mmk2410](https://twitter.com/mmk2410) - [Twitter @mmk2410](https://twitter.com/mmk2410)
- [Blog mmk2410.org](https://mmk2410.org/), you can view the current posts about Rangitaki on [this page](https://mmk2410.org/tag/rangitaki/). - [Google+ +MarcelKapfer](https://plus.google.com/+MarcelMichaelKapfer/posts)

View file

@ -39,8 +39,6 @@ $themes = getDir('./themes');
$yaml["design"]["theme"] = get("Which theme would you like to use? (" . $themes . ")", $yaml["design"]["theme"], "material-light"); $yaml["design"]["theme"] = get("Which theme would you like to use? (" . $themes . ")", $yaml["design"]["theme"], "material-light");
$yaml["design"]["pagination"] = $yaml["design"]["pagination"] =
get("Which posts should be displayed on one page (0 to disable)", $yaml["design"]["pagination"], "0"); get("Which posts should be displayed on one page (0 to disable)", $yaml["design"]["pagination"], "0");
$yaml["design"]["excerpt"] =
getBool("Should be overview pages only show a excerpt of the full posts? (on/off)", $yaml["design"]["excerpt"], "off");
$yaml["design"]["favicon"] = get("URL to your favicon", $yaml["design"]["favicon"], "https://example.com/fav.ico"); $yaml["design"]["favicon"] = get("URL to your favicon", $yaml["design"]["favicon"], "https://example.com/fav.ico");
// rcc // rcc
@ -51,9 +49,6 @@ $yaml["rcc"]["api"] = "off";
$langs = getDir('./lang'); $langs = getDir('./lang');
$yaml["language"] = get("Choose a language (" . $langs . ")", $yaml["language"], "en"); $yaml["language"] = get("Choose a language (" . $langs . ")", $yaml["language"], "en");
// social media
$yaml["social"]["twitter"] = get("Your Twitter username:", $yaml["social"]["twitter"], "");
$config->writeConfig($yaml); $config->writeConfig($yaml);
function get($question, $value, $default) function get($question, $value, $default)

View file

@ -23,14 +23,8 @@ if ($password == "") {
exit(); exit();
} }
$options = [
'cost' => 12
];
$password = password_hash($password, PASSWORD_BCRYPT, $options);
$username = '$username = "' . $username . '";'; $username = '$username = "' . $username . '";';
$password = '$password = \'' . $password . '\';'; $password = '$password = "' . $password . '";';
$file = '<?php' . "\n" . $username . "\n" . $password . "\n"; $file = '<?php' . "\n" . $username . "\n" . $password . "\n";

View file

@ -1,2 +0,0 @@
%TITLE: Docs
%URL: https://mmk2410.org/rangitaki/docs/

View file

@ -2,8 +2,13 @@
This is the Blog of the Blog Engine **Rangitaki** This is the Blog of the Blog Engine **Rangitaki**
Rangitaki is based on PHP and Markdown. It is easy to install and to configure. Rangitaki is based on PHP, XML and Markdown. It is easy to install and to configure.
The latest version of Rangitaki is 1.4.3. The latest Version of the 0.2 series is **0.2.2**
You can find the source code on [GitLab](https://gitlab.com/mmk2410/rangitaki). The latest Version of the development stream isn' ready yet.
** !IMPORTANT! Please read [this](index.php?article=About-the-Future-of-pBlog) for more information about the different versions of pBlog.**
You can find the source code on [GitHub](https://github.com/mmk2410/Rangitaki).

View file

@ -1,15 +0,0 @@
#!/bin/bash
# Only install dependencies for Docker
[[ ! -e /.dockerenv ]] && [[ ! -e /.dockerinit ]] && exit 0
set -xe
# Install git
apt-get update -yqq
apt-get install git -yqq
# Install phpunit
curl -o /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
chmod +x /usr/local/bin/phpunit

144
composer.lock generated
View file

@ -4,8 +4,58 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "ccdcfdb56ed68253e2388261fbb6d1b3", "hash": "bf0772b9501ce6231c06bfbcdb671d1d",
"content-hash": "49b3f5550e60b62ffeb5306a75a87d97",
"packages": [ "packages": [
{
"name": "bshaffer/oauth2-server-php",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/bshaffer/oauth2-server-php.git",
"reference": "058c98f73209f9c49495e1799d32c035196fe8b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/058c98f73209f9c49495e1799d32c035196fe8b8",
"reference": "058c98f73209f9c49495e1799d32c035196fe8b8",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"suggest": {
"aws/aws-sdk-php": "~2.8 is required to use the DynamoDB storage engine",
"firebase/php-jwt": "~2.2 is required to use JWT features",
"predis/predis": "Required to use the Redis storage engine",
"thobbs/phpcassa": "Required to use the Cassandra storage engine"
},
"type": "library",
"autoload": {
"psr-0": {
"OAuth2": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brent Shaffer",
"email": "bshafs@gmail.com",
"homepage": "http://brentertainment.com"
}
],
"description": "OAuth2 Server for PHP",
"homepage": "http://github.com/bshaffer/oauth2-server-php",
"keywords": [
"auth",
"oauth",
"oauth2"
],
"time": "2015-09-18 18:05:10"
},
{ {
"name": "codeguy/upload", "name": "codeguy/upload",
"version": "1.3.2", "version": "1.3.2",
@ -51,7 +101,7 @@
"upload", "upload",
"validation" "validation"
], ],
"time": "2013-07-07T17:01:41+00:00" "time": "2013-07-07 17:01:41"
}, },
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
@ -78,25 +128,22 @@
"MIT" "MIT"
], ],
"description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
"time": "2014-12-30T15:22:37+00:00" "time": "2014-12-30 15:22:37"
}, },
{ {
"name": "erusev/parsedown", "name": "erusev/parsedown",
"version": "1.6.1", "version": "1.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/erusev/parsedown.git", "url": "https://github.com/erusev/parsedown.git",
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb" "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb", "url": "https://api.github.com/repos/erusev/parsedown/zipball/3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb", "reference": "3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7",
"shasum": "" "shasum": ""
}, },
"require": {
"php": ">=5.3.0"
},
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-0": { "psr-0": {
@ -120,20 +167,20 @@
"markdown", "markdown",
"parser" "parser"
], ],
"time": "2016-11-02T15:56:58+00:00" "time": "2015-10-04 16:44:32"
}, },
{ {
"name": "fguillot/picofeed", "name": "fguillot/picofeed",
"version": "v0.1.28", "version": "v0.1.23",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fguillot/picoFeed.git", "url": "https://github.com/fguillot/picoFeed.git",
"reference": "9da506c308bcb40b6fc630f9123466028c03170b" "reference": "a7c3d420c239fe9ffc39b0d06b6e57db39ce3797"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/9da506c308bcb40b6fc630f9123466028c03170b", "url": "https://api.github.com/repos/fguillot/picoFeed/zipball/a7c3d420c239fe9ffc39b0d06b6e57db39ce3797",
"reference": "9da506c308bcb40b6fc630f9123466028c03170b", "reference": "a7c3d420c239fe9ffc39b0d06b6e57db39ce3797",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -145,11 +192,6 @@
"php": ">=5.3.0", "php": ">=5.3.0",
"zendframework/zendxml": "^1.0" "zendframework/zendxml": "^1.0"
}, },
"require-dev": {
"phpdocumentor/reflection-docblock": "2.0.4",
"phpunit/phpunit": "4.8.26",
"symfony/yaml": "2.8.7"
},
"suggest": { "suggest": {
"ext-curl": "PicoFeed will use cURL if present" "ext-curl": "PicoFeed will use cURL if present"
}, },
@ -173,20 +215,20 @@
], ],
"description": "Modern library to handle RSS/Atom feeds", "description": "Modern library to handle RSS/Atom feeds",
"homepage": "https://github.com/fguillot/picoFeed", "homepage": "https://github.com/fguillot/picoFeed",
"time": "2016-12-29T00:06:41+00:00" "time": "2016-04-17 22:31:55"
}, },
{ {
"name": "nikic/fast-route", "name": "nikic/fast-route",
"version": "v1.1.0", "version": "v0.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/FastRoute.git", "url": "https://github.com/nikic/FastRoute.git",
"reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3" "reference": "31fa86924556b80735f98b294a7ffdfb26789f22"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/f3dcf5130e634b6123d40727d612ec6aa4f61fb3", "url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22",
"reference": "f3dcf5130e634b6123d40727d612ec6aa4f61fb3", "reference": "31fa86924556b80735f98b294a7ffdfb26789f22",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -216,7 +258,7 @@
"router", "router",
"routing" "routing"
], ],
"time": "2016-10-20T17:36:47+00:00" "time": "2015-06-18 19:15:47"
}, },
{ {
"name": "pimple/pimple", "name": "pimple/pimple",
@ -262,20 +304,20 @@
"container", "container",
"dependency injection" "dependency injection"
], ],
"time": "2015-09-11T15:10:35+00:00" "time": "2015-09-11 15:10:35"
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "1.0.1", "version": "1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-message.git", "url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -303,7 +345,6 @@
} }
], ],
"description": "Common interface for HTTP messages", "description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [ "keywords": [
"http", "http",
"http-message", "http-message",
@ -312,32 +353,29 @@
"request", "request",
"response" "response"
], ],
"time": "2016-08-06T14:39:51+00:00" "time": "2015-05-04 20:22:00"
}, },
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.7.0", "version": "3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/slimphp/Slim.git", "url": "https://github.com/slimphp/Slim.git",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef" "reference": "939f2e85d57508de9cff241d10091cd972f221c3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/4254e40d81559e35cdf856bcbaca5f3af468b7ef", "url": "https://api.github.com/repos/slimphp/Slim/zipball/939f2e85d57508de9cff241d10091cd972f221c3",
"reference": "4254e40d81559e35cdf856bcbaca5f3af468b7ef", "reference": "939f2e85d57508de9cff241d10091cd972f221c3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"container-interop/container-interop": "^1.1", "container-interop/container-interop": "^1.1",
"nikic/fast-route": "^1.0", "nikic/fast-route": "^0.6",
"php": ">=5.5.0", "php": ">=5.5.0",
"pimple/pimple": "^3.0", "pimple/pimple": "^3.0",
"psr/http-message": "^1.0" "psr/http-message": "^1.0"
}, },
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.0", "phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.5" "squizlabs/php_codesniffer": "^2.5"
@ -375,42 +413,36 @@
} }
], ],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
"homepage": "https://slimframework.com", "homepage": "http://slimframework.com",
"keywords": [ "keywords": [
"api", "api",
"framework", "framework",
"micro", "micro",
"router" "router"
], ],
"time": "2016-12-20T20:30:47+00:00" "time": "2016-03-10 21:37:40"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.2.1", "version": "v3.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "a7095af4b97a0955f85c8989106c249fa649011f" "reference": "0047c8366744a16de7516622c5b7355336afae96"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f", "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
"reference": "a7095af4b97a0955f85c8989106c249fa649011f", "reference": "0047c8366744a16de7516622c5b7355336afae96",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": ">=5.5.9"
}, },
"require-dev": {
"symfony/console": "~2.8|~3.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.2-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -437,7 +469,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-12-10T10:07:06+00:00" "time": "2016-03-04 07:55:57"
}, },
{ {
"name": "zendframework/zendxml", "name": "zendframework/zendxml",
@ -482,7 +514,7 @@
"xml", "xml",
"zf2" "zf2"
], ],
"time": "2016-02-04T21:02:08+00:00" "time": "2016-02-04 21:02:08"
} }
], ],
"packages-dev": [], "packages-dev": [],

View file

@ -9,19 +9,15 @@ blog:
intro: 'on' intro: 'on'
disqus: rangitaki disqus: rangitaki
analytics: '' analytics: ''
footer: "Rangitaki 2016 <a href=\"https://gitlab.com/mmk2410/rangitaki\" target=\"blank\">\n gitlab.com/mmk2410/rangitaki</a>" footer: "Rangitaki 2016 <a href=\"https://github.com/mmk2410/Rangitaki\" target=\"blank\">\n github.com/mmk2410/Rangitaki</a>"
url: 'https://example.com/blog/' url: 'https://example.com/blog/'
design: design:
excerpt: "off"
fab: 'on' fab: 'on'
drawer: 'on' drawer: 'on'
theme: material-light theme: material-light
pagination: 0 pagination: 0
excerpt: 'off'
favicon: 'http://example.com/res/img/favicon.png' favicon: 'http://example.com/res/img/favicon.png'
rcc: rcc:
rcc: 'on' rcc: 'on'
api: 'on' api: 'on'
language: en language: en
social:
twitter: ''

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View file

@ -25,10 +25,7 @@ date_default_timezone_set('UTC');
require __DIR__ . '/vendor/autoload.php'; // loading composer libs require __DIR__ . '/vendor/autoload.php'; // loading composer libs
require './res/php/Config.php'; require './res/php/Config.php';
require_once './res/php/BlogListGenerator.php';
use mmk2410\rbe\config\Config as Config; use mmk2410\rbe\config\Config as Config;
use mmk2410\rbe\BlogListGenerator\BlogListGenerator as BlogListGenerator;
$configParser = new Config('config.yaml', 'vendor/autoload.php'); $configParser = new Config('config.yaml', 'vendor/autoload.php');
@ -36,7 +33,7 @@ $config = $configParser->getConfig();
require './lang/' . $config["language"] . ".php"; // Language file require './lang/' . $config["language"] . ".php"; // Language file
require_once 'res/php/ArticleGenerator.php'; // The article generator require_once 'res/php/ArticleGenerator.php'; // The article generator
require_once './res/php/BlogListGenerator.php'; // and the blog list generator
// Getting some variables ($_GET and $_SERVER) // Getting some variables ($_GET and $_SERVER)
$getblog = filter_input(INPUT_GET, "blog"); // get the blog variable $getblog = filter_input(INPUT_GET, "blog"); // get the blog variable
@ -117,9 +114,10 @@ $feedurl = $config["blog"]["url"] . "/feed/" . $blog . ".atom";
<meta property="og:url" content="<?php echo $url; ?>"/> <meta property="og:url" content="<?php echo $url; ?>"/>
<meta property="og:image" content="<?php echo $config['design']['favicon']; ?>"/> <meta property="og:image" content="<?php echo $config['design']['favicon']; ?>"/>
<meta property="og:description" content="<?php echo $config['blog']['description']; ?>"/> <meta property="og:description" content="<?php echo $config['blog']['description']; ?>"/>
<meta property="og:locale:alternate" content="<?php echo $lang; ?>"/>
<!-- Twitter meta tags --> <!-- Twitter meta tags -->
<meta name="twitter:card" content="summary"/> <meta name="twitter:card" content="summary"/>
<meta name="twitter:site" content="<?php echo $config['social']['twitter']; ?>"/> <meta name="twitter:site" content="<?php echo $twitter; ?>"/>
<meta name="twitter:title" content="<?php echo $hd_subblog_title; ?>"/> <meta name="twitter:title" content="<?php echo $hd_subblog_title; ?>"/>
<meta name="twitter:description" content="<?php echo $config['blog']['description']; ?>"/> <meta name="twitter:description" content="<?php echo $config['blog']['description']; ?>"/>
<meta name="twitter:image" content="<?php echo $config['design']['favicon']; ?>"/> <meta name="twitter:image" content="<?php echo $config['design']['favicon']; ?>"/>
@ -185,7 +183,7 @@ if ($config["design"]["drawer"] == "on") {
echo "<section>"; echo "<section>";
// 1. Set localized string 2. Set blogtitle // 1. Set localized string 2. Set blogtitle
echo "<div class='nav-item-static'>" . echo "<div class='nav-item-static'>" .
$BLOGLANG['Blogs on'] . " " . $config["blog"]["title"] . $BLOGLANG['Blogs on'] . $config["blog"]["title"] .
":</div>"; ":</div>";
// iterating through the blogs/ directory // iterating through the blogs/ directory
foreach ($blogs as $navblog) { foreach ($blogs as $navblog) {
@ -195,10 +193,8 @@ if ($config["design"]["drawer"] == "on") {
if ($getblog == "") { // Run when on main blog if ($getblog == "") { // Run when on main blog
if ($navblog != "main.md") { // excluding main blog if ($navblog != "main.md") { // excluding main blog
// creating navigation item // creating navigation item
echo BlogListGenerator::listBlog( BlogListGenerator::listBlog(
"./blogs/", "./blogs/", $navblog, $config["blog"]["title"]
$navblog,
$config["blog"]["title"]
); );
} }
} else { } else {
@ -206,10 +202,8 @@ if ($config["design"]["drawer"] == "on") {
// -> this blog will be excluded // -> this blog will be excluded
if ($getblog . ".md" != $navblog) { if ($getblog . ".md" != $navblog) {
// creating navigation item // creating navigation item
echo BlogListGenerator::listBlog( BlogListGenerator::listBlog(
"./blogs/", "./blogs/", $navblog, $blogmaintitle
$navblog,
$blogmaintitle
); );
} }
} }
@ -220,7 +214,7 @@ if ($config["design"]["drawer"] == "on") {
// If viewing a blog or a tag // If viewing a blog or a tag
?> ?>
<!-- Set a back item instead of the blogs. --> <!-- Set a back item instead of the blogs. -->
<a class="nav-item" onclick="history.go(-1);">Go back</a> <a class="nav-item" onclick="goBack()">Go back</a>
<?php <?php
} }
if ($config["blog"]["home"] == "on") { // If a blog home is existend if ($config["blog"]["home"] == "on") { // If a blog home is existend
@ -325,11 +319,7 @@ if ($config["design"]["drawer"] == "on") {
if (strlen($article) >= 3 && substr($article, -3) == ".md") { if (strlen($article) >= 3 && substr($article, -3) == ".md") {
// generate the article // generate the article
ArticleGenerator::newArticle( ArticleGenerator::newArticle(
$articlesdir, $articlesdir, $article, $getblog
$article,
$getblog,
$config["design"]["excerpt"],
$BLOGLANG["Read More"]
); );
} }
} }
@ -347,20 +337,12 @@ if ($config["design"]["drawer"] == "on") {
if ($config["design"]["pagination"]) { if ($config["design"]["pagination"]) {
if ($posts_amount < $pag_max && $posts_amount >= $pag_min) { if ($posts_amount < $pag_max && $posts_amount >= $pag_min) {
ArticleGenerator::newArticle( ArticleGenerator::newArticle(
$articlesdir, $articlesdir, $article, $getblog
$article,
$getblog,
$config["design"]["excerpt"],
$BLOGLANG["Read More"]
); );
} }
} else { } else {
ArticleGenerator::newArticle( ArticleGenerator::newArticle(
$articlesdir, $articlesdir, $article, $getblog
$article,
$getblog,
$config["design"]["excerpt"],
$BLOGLANG["Read More"]
); );
} }
} }
@ -372,11 +354,7 @@ if ($config["design"]["drawer"] == "on") {
} elseif (isset($getarticle)) { // ARTICLE VIEW } elseif (isset($getarticle)) { // ARTICLE VIEW
// generate the requested article // generate the requested article
ArticleGenerator::newArticle( ArticleGenerator::newArticle(
$articlesdir, $articlesdir, $getarticle . ".md", $getblog
$getarticle . ".md",
$getblog,
'off',
$BLOGLANG["Read More"]
); );
include './res/php/Disqus.php'; // include disques include './res/php/Disqus.php'; // include disques
} else { // SOMETHING STRANGE: THIS SHOULDN'T HAPPEN } else { // SOMETHING STRANGE: THIS SHOULDN'T HAPPEN
@ -439,14 +417,14 @@ if ($config["design"]["drawer"] == "on") {
<script src="./res/js/app.js"></script> <!--include main javascript--> <script src="./res/js/app.js"></script> <!--include main javascript-->
<!-- JS extension support --> <!-- JS extension support -->
<?php <?php
if (file_exists("./extensions")) { if(file_exists("./extensions")) {
$extensions = scandir('./extensions'); $extensions = scandir('./extensions');
foreach ($extensions as $extension) { foreach ($extensions as $extension) {
if (substr($extension, -3) == ".js") { if (substr($extension, -3) == ".js") {
echo "<script src='./extensions/$extension'></script>"; echo "<script src='./extensions/$extension'></script>";
}
} }
} }
}
?> ?>
<?php <?php
require './res/php/GoogleAnalytics.php'; // include google analytics require './res/php/GoogleAnalytics.php'; // include google analytics

View file

@ -8,5 +8,4 @@ $BLOGLANG = [
"Check out" => "Schau dir das an:", "Check out" => "Schau dir das an:",
"Next Page" => "Nächste Seite", "Next Page" => "Nächste Seite",
"Previous Page" => "Vorherige Seite", "Previous Page" => "Vorherige Seite",
"Read More" => "Weiterlesen",
]; ];

View file

@ -8,5 +8,4 @@ $BLOGLANG = [
"Check out" => "Check out:", "Check out" => "Check out:",
"Next Page" => "Next Page", "Next Page" => "Next Page",
"Previous Page" => "Previous Page", "Previous Page" => "Previous Page",
"Read More" => "Read More",
]; ];

View file

@ -1,6 +1,6 @@
{ {
"name": "rangitaki", "name": "rangitaki",
"version": "1.5.0", "version": "1.4.0",
"description": "A simple PHP blogging engine without any database dependencies", "description": "A simple PHP blogging engine without any database dependencies",
"main": "index.php", "main": "index.php",
"scripts": { "scripts": {
@ -21,7 +21,6 @@
}, },
"homepage": "https://gitlab.com/mmk2410/rangitaki#README", "homepage": "https://gitlab.com/mmk2410/rangitaki#README",
"devDependencies": { "devDependencies": {
"coffee-script": "^1.10.0",
"del": "^2.2.0", "del": "^2.2.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-coffee": "^2.3.2", "gulp-coffee": "^2.3.2",
@ -31,7 +30,6 @@
"gulp-size": "^2.1.0", "gulp-size": "^2.1.0",
"gulp-sourcemaps": "^2.0.0-alpha", "gulp-sourcemaps": "^2.0.0-alpha",
"gulp-uglify": "^1.5.3", "gulp-uglify": "^1.5.3",
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0"
"node-sass": "^3.7.0"
} }
} }

View file

@ -0,0 +1,52 @@
<?php
/**
* PHP Version 7
*
* Authentication Helper Class
*
* @category Authentication
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
namespace mmk2410\rbe\digestAuth;
/**
* PHP Version 7
*
* Authentication Helper Class
*
* @category Authentication
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
class DigestAuth
{
/**
* parser for http digest
*
* @param $txt data to parse
*
* @return parsed data or FALSE
*/
public function httpDigestParse($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}

View file

@ -1,22 +1,39 @@
<?php <?php
// Marcel Kapfer (mmk2410) // Marcel Kapfer (mmk2410)
// License: MIT License // License: MIT License
// HTTP Basic Auth for the API // api digest auth
$basedir = "../../../"; require 'DigestAuth.php';
require '../../ssl.php';
require '../../password.php'; require '../../password.php';
if (!isset($_SERVER['PHP_AUTH_USER'])) { use \mmk2410\rbe\digestAuth\DigestAuth as DigestAuth;
header('WWW-Authenticate: Basic realm="RCC API"');
$realm = 'Restricted area';
$users = array($username => $password);
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized'); header('HTTP/1.1 401 Unauthorized');
echo "Access denied to the RCC API!"; header('WWW-Authenticate: Digest realm="'.$realm.
exit; '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
} elseif ($_SERVER['PHP_AUTH_USER'] != $username ||
!password_verify($_SERVER['PHP_AUTH_PW'], $password)) { die('Access to RCC API not granted');
header('HTTP/1.1 401 Unauthorized'); }
echo "Wrong credentials: Access denied!";
exit;
// analyze the PHP_AUTH_DIGEST variable
if (!($data = DigestAuth::httpDigestParse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']])) {
die('Wrong Credentials!');
}
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response) {
die('Wrong Credentials!');
} }

View file

@ -9,13 +9,13 @@ use \Psr\Http\Message\ResponseInterface as Response;
require '../../../vendor/autoload.php'; require '../../../vendor/autoload.php';
require '../../../res/php/Config.php'; require '../../../res/php/Config.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config; use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php'); $config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig(); $settings = $config->getConfig();
include '../auth/auth.php';
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") { if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App(); $app = new \Slim\App();

View file

@ -10,13 +10,13 @@ require '../../../vendor/autoload.php';
require '../../../res/php/Config.php'; require '../../../res/php/Config.php';
require '../../../res/php/ArticleGenerator.php'; require '../../../res/php/ArticleGenerator.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config; use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php'); $config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig(); $settings = $config->getConfig();
include '../auth/auth.php';
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") { if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App(); $app = new \Slim\App();

View file

@ -10,13 +10,13 @@ require '../../../vendor/autoload.php';
require '../../../res/php/Config.php'; require '../../../res/php/Config.php';
require '../../../res/php/ArticleGenerator.php'; require '../../../res/php/ArticleGenerator.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config; use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php'); $config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig(); $settings = $config->getConfig();
include '../auth/auth.php';
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") { if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App(); $app = new \Slim\App();

View file

@ -6,13 +6,13 @@
* @package Rcc * @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz> * @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License * @license MIT License
* @link https://gitlab.com/mmk2410/rangitaki * @link https://github.com/mmk2410/rangitaki
* *
* Feed Generator * Feed Generator
* *
* The MIT License * The MIT License
* *
* Copyright 2015 - 2016 (c) mmk2410. * Copyright 2015 mmk2410.
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -36,35 +36,27 @@
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
require "../../vendor/autoload.php"; require "../../vendor/autoload.php";
require "../../res/php/Config.php"; require_once "../../config.php";
require_once "../../res/php/ArticleGenerator.php"; require_once "../../res/php/ArticleGenerator.php";
use PicoFeed\Syndication\AtomFeedBuilder; use PicoFeed\Syndication\Atom;
use PicoFeed\Syndication\AtomItemBuilder;
use \mmk2410\rbe\config\Config as Config;
$config = new Config('../../config.yaml', '../../vendor/autoload.php');
$settings = $config->getConfig();
include '../ssl.php';
session_start(); session_start();
if ($_SESSION['login']) { if ($_SESSION['login']) {
$art_dir = "./../../articles/" . $_GET['blog'] . "/"; $art_dir = "./../../articles/" . $_GET['blog'] . "/";
$feed_path = "./../../feed/" . $_GET['blog'] . ".atom"; $feed_path = "./../../feed/" . $_GET['blog'] . ".atom";
if ($_GET['blog'] == "main") { $writer = new Atom();
$blogtitle = $settings['blog']['title'];
} else {
$blogtitl = $settings['blog']['title'] . " - " . ucwords($_GET['blog']);
}
$feedBuilder = AtomFeedBuilder::create() $writer->title = $blogtitle;
->withTitle($blogtitle) $writer->site_url = $blogurl;
->withAuthor($settings['blog']['author']) $writer->feed_url = $blogurl . "/feed/" . $_GET['blog'] . ".atom";
->withFeedUrl($settings['blog']['url'] . "/feed/" . $_GET['blog'] . ".atom") $writer->author = array(
->withSiteUrl($settings['blog']['url']) 'name' => $blogauthor,
->withDate(new DateTime(date(DATE_ATOM))); 'url' => $blogurl,
'email' => ''
);
$articles = scandir($art_dir, 1); $articles = scandir($art_dir, 1);
@ -79,45 +71,25 @@ if ($_SESSION['login']) {
$text = Parsedown::instance() $text = Parsedown::instance()
->setBreaksEnabled(true)// with linebreaks ->setBreaksEnabled(true)// with linebreaks
->text($file); ->text($file);
if (new DateTime(date(DATE_ATOM, strtotime($datestring))) != null) { $writer->items[] = array(
$date = new DateTime( 'title' => ArticleGenerator::getTitle($art_dir, $article),
date( 'updated' => strtotime(
DATE_ATOM, ArticleGenerator::getDate($art_dir . $article)
strtotime($datestring) ),
) 'url' => $blogurl . "./?article=" .
); substr($article, 0, strlen($article) - 3),
} else { 'summary'=> ArticleGenerator::getSummary(
$date = new DateTime(date(DATE_ATOM)); $art_dir, $article
} ),
$date = new DateTime(date(DATE_ATOM)); 'content' => $text
$feedBuilder );
->withItem(AtomItemBuilder::create($feedBuilder)
->withTitle(
ArticleGenerator::getTitle($art_dir, $article)
)
->withUrl(
$settings['blog']['url'] . "./?article=" . substr($article, 0, strlen($article) - 3)
)
->withAuthor(
ArticleGenerator::getAuthor($art_dir, $article)
)
->withPublishedDate(
parseDate(ArticleGenerator::getDate($art_dir, $article))
)
->withUpdatedDate(
parseDate(ArticleGenerator::getDate($art_dir, $article))
)
->withSummary(
ArticleGenerator::getSummary($art_dir, $article)
)
->withContent($text));
$amount += 1; $amount += 1;
} }
} }
} }
$feed = $feedBuilder->build(); $feed = $writer->execute();
$file = fopen($feed_path, "w"); $file = fopen($feed_path, "w");
@ -130,19 +102,4 @@ if ($_SESSION['login']) {
echo "0"; echo "0";
} }
?>
function parseDate($datestring)
{
$datetime = new DateTime(date(DATE_ATOM));
try {
$datetime = new DateTime(
date(
DATE_ATOM,
strtotime($datestring)
)
);
} catch (Exception $e) {
$datetime = new DateTime(date(DATE_ATOM));
}
return $datetime;
}

View file

@ -32,19 +32,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
require '../vendor/autoload.php';
require '../res/php/Config.php';
require "./../res/php/BlogListGenerator.php";
use \mmk2410\rbe\config\Config as Config;
use mmk2410\rbe\BlogListGenerator\BlogListGenerator as BlogListGenerator;
$config = new Config("../config.yaml", '../vendor/autoload.php');
$settings = $config->getConfig();
include './ssl.php';
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@ -77,7 +64,12 @@ include './ssl.php';
<div class="main"> <div class="main">
<?php <?php
$config = $settings; require '../res/php/Config.php';
use mmk2410\rbe\config\Config as Config;
$configParser = new Config('../config.yaml', '../vendor/autoload.php');
$config = $configParser->getConfig();
if ($config["rcc"]["rcc"] == "on") { if ($config["rcc"]["rcc"] == "on") {
include 'password.php'; include 'password.php';
@ -108,9 +100,9 @@ if ($config["rcc"]["rcc"] == "on") {
<?php <?php
} else { } else {
if (password_verify($passwd, $password)) { if ($passwd == $password) {
$_SESSION['login'] = true; $_SESSION['login'] = true;
include_once "./../res/php/BlogListGenerator.php";
?> ?>
<!-- Post Upload --> <!-- Post Upload -->
@ -124,10 +116,8 @@ if ($config["rcc"]["rcc"] == "on") {
$blogs = scandir("../blogs/"); $blogs = scandir("../blogs/");
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") { if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
if (BlogListGenerator::getExternalLink($blog, '../') == null) { $blog = substr($blog, 0, -3);
$blog = substr($blog, 0, -3); echo "<option value='$blog'>$blog</option>";
echo "<option value='$blog'>$blog</option>";
}
} }
} }
?> ?>
@ -151,10 +141,8 @@ if ($config["rcc"]["rcc"] == "on") {
$blogs = scandir("../blogs/"); $blogs = scandir("../blogs/");
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") { if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
if (BlogListGenerator::getExternalLink($blog, '../') == null) { $blog = substr($blog, 0, -3);
$blog = substr($blog, 0, -3); echo "<option value='$blog'>$blog</option>";
echo "<option value='$blog'>$blog</option>";
}
} }
} }
?> ?>
@ -201,10 +189,8 @@ if ($config["rcc"]["rcc"] == "on") {
$blogs = scandir("../blogs/"); $blogs = scandir("../blogs/");
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") { if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
if (BlogListGenerator::getExternalLink($blog, '../') == null) { $blog = substr($blog, 0, -3);
$blog = substr($blog, 0, -3); echo "<option value='$blog'>$blog</option>";
echo "<option value='$blog'>$blog</option>";
}
} }
} }
?> ?>
@ -226,10 +212,8 @@ if ($config["rcc"]["rcc"] == "on") {
$blogs = scandir("../blogs/"); $blogs = scandir("../blogs/");
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") { if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
if (BlogListGenerator::getExternalLink($blog, '../') == null) { $blog = substr($blog, 0, -3);
$blog = substr($blog, 0, -3); echo "<option value='$blog'>$blog</option>";
echo "<option value='$blog'>$blog</option>";
}
} }
} }
?> ?>
@ -259,10 +243,8 @@ if ($config["rcc"]["rcc"] == "on") {
$blogs = scandir("../blogs/"); $blogs = scandir("../blogs/");
foreach ($blogs as $blog) { foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") { if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
if (BlogListGenerator::getExternalLink($blog, '../') == null) { $blog = substr($blog, 0, -3);
$blog = substr($blog, 0, -3); echo "<option value='$blog'>$blog</option>";
echo "<option value='$blog'>$blog</option>";
}
} }
} }
?> ?>

View file

@ -1,3 +1,3 @@
<?php <?php
$username = "test"; $username = "example";
$password = '$2y$12$nHitKTwHqU4GmI3ADVE05eH/723fCNgdQ65kQ53FyZUVVB03BjfCO'; $password = "example";

View file

@ -1,11 +0,0 @@
<?php
// Marcel Kapfer (mmk2410) / Wilson O'Sullivan
// License: MIT License
// SSL Verification
if (isset($settings["rcc"]["debug"]) && $settings["rcc"]["debug"] != "on") {
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") {
header('HTTP/1.1 400 Bad Request');
exit();
}
}

View file

@ -1,3 +1,3 @@
body{margin-top:94px}.main{height:100%;margin-left:0}.overlay{opacity:0;position:fixed;top:0;left:0;background-color:black;width:100%;z-index:30;height:100%;display:none}.header{top:0;right:0;left:0;width:100%;height:64px;position:absolute}.title{color:#fff;font-size:23px;text-decoration:none;line-height:64px;vertical-align:middle;left:75px}.title>a{text-decoration:none;color:#fff}.fadeout{position:absolute;height:64px;top:0;right:0;width:40px}.nav-img{height:26px;padding:19px;cursor:pointer}.nav{width:300px;position:fixed;height:100%;top:0;left:-301px;z-index:40}.nav-item,.nav-item-static{text-decoration:none;text-indent:0;display:inline-block;height:48px;vertical-align:middle;width:284px;line-height:48px;padding-left:16px;transition:background-color 125ms ease-in-out 0ms}.nav-close{cursor:pointer}.nav-close-img{height:35px;padding:12px}.nav-item{cursor:pointer}.divider{width:100%}.card{margin-right:auto;margin-left:auto;width:75%;padding:24px;margin-bottom:40px;max-width:1160px}.card a{-moz-hyphens:auto;-epub-hyphens:auto;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;word-wrap:break-word}.headline{display:block;padding-bottom:8px}.card img{max-width:100%;max-height:400px;display:block;margin-left:auto;margin-right:auto}.author{display:block}.readmore{padding:12px 0 25px 0}.fabmenu{position:fixed;bottom:20px;right:20px}.fab{height:60px;width:60px;border-radius:30px;cursor:pointer}.fab-img{width:28px;padding:15px}.subfab{height:45px;width:45px;border-radius:30px;margin-right:auto;margin-left:auto;margin-bottom:25px;display:none}.subfab-img{width:22px;padding:12px}.pag_buttons{margin-right:auto;margin-left:auto;width:calc(75% + 48px);margin-bottom:80px;max-width:1160px;text-align:right}.button{text-decoration:none;color:#fff;line-height:36px;min-width:64px;text-align:center;height:36px;padding:8px;margin-top:4px;margin-bottom:5px;cursor:pointer}.pag_next{margin-left:8px}.footer a{transition:border-bottom-color 150ms ease-in-out 100ms}.footer a:hover{border-bottom-color:#383838}@media screen and (min-width: 1440px){.nav{left:0;padding-top:64px}.nav-close-img{display:none}.nav-close{display:none}.nav-img{display:none}.header{left:300px}.main{margin-left:300px;width:calc(100% - 300px)}}@media screen and (max-width: 720px){.card{width:82%}.pag_buttons{width:calc(82% + 48px)}} body{margin-top:94px}.main{height:100%;margin-left:0}.overlay{opacity:0;position:fixed;top:0;left:0;background-color:black;width:100%;z-index:30;height:100%;display:none}.header{top:0;right:0;left:0;width:100%;height:64px;position:absolute}.title{color:#fff;font-size:23px;text-decoration:none;line-height:64px;vertical-align:middle;left:75px}.title>a{text-decoration:none;color:#fff}.fadeout{position:absolute;height:64px;top:0;right:0;width:40px}.nav-img{height:26px;padding:19px;cursor:pointer}.nav{width:300px;position:fixed;height:100%;top:0;left:-301px;z-index:40}.nav-item,.nav-item-static{text-decoration:none;text-indent:0;display:inline-block;height:48px;vertical-align:middle;width:284px;line-height:48px;padding-left:16px;transition:background-color 125ms ease-in-out 0ms}.nav-close{cursor:pointer}.nav-close-img{height:35px;padding:12px}.nav-item{cursor:pointer}.divider{width:100%}.card{margin-right:auto;margin-left:auto;width:75%;padding:24px;margin-bottom:40px;max-width:1160px}.card a{-moz-hyphens:auto;-epub-hyphens:auto;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;word-wrap:break-word}.headline{display:block;padding-bottom:8px}.card img{max-width:100%;max-height:400px;display:block;margin-left:auto;margin-right:auto}.author{display:block}.fabmenu{position:fixed;bottom:20px;right:20px}.fab{height:60px;width:60px;border-radius:30px;cursor:pointer}.fab-img{width:28px;padding:15px}.subfab{height:45px;width:45px;border-radius:30px;margin-right:auto;margin-left:auto;margin-bottom:25px;display:none}.subfab-img{width:22px;padding:12px}.pag_buttons{margin-right:auto;margin-left:auto;width:calc(75% + 48px);margin-bottom:80px;max-width:1160px;text-align:right}.button{text-decoration:none;color:#fff;line-height:36px;min-width:64px;text-align:center;height:36px;padding:8px;margin-top:4px;margin-bottom:5px;cursor:pointer}.pag_next{margin-left:8px}.footer a{transition:border-bottom-color 150ms ease-in-out 100ms}.footer a:hover{border-bottom-color:#383838}@media screen and (min-width: 1440px){.nav{left:0;padding-top:64px}.nav-close-img{display:none}.nav-close{display:none}.nav-img{display:none}.header{left:300px}.main{margin-left:300px;width:calc(100% - 300px)}}@media screen and (max-width: 720px){.card{width:82%}.pag_buttons{width:calc(82% + 48px)}}
/*# sourceMappingURL=rangitaki.css.map */ /*# sourceMappingURL=rangitaki.css.map */

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
(function(){var n,t,a,e,r;e=function(){var e,i;return e=!1,$(".fabmenu").click(function(){return e?(a(),e=!1):(t(),e=!0)}),i=!1,$(".nav-img, .overlay, .nav-close").click(function(){return i?(n(),i=!1):(r(),i=!0)}),$(document).keyup(function(t){return i&&27===t.which?(n(),i=!1):i||77!==t.which?void 0:(r(),i=!0)}),$(".articletext a").attr("target","_blank")},$(document).ready(e),t=function(){return $(".subfab").fadeIn(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/close.svg"),$(".fab-img").fadeIn(60)})},a=function(){return $(".subfab").fadeOut(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/share.svg"),$(".fab-img").fadeIn(60)})},r=function(){return $(".nav").animate({left:"0px"},125),$(".overlay").show(),$(".overlay").animate({opacity:"0.4"},125)},n=function(){return $(".nav").animate({left:"-301px"},125),$(".overlay").animate({opacity:"0.0"},125,function(){return $(".overlay").css({display:"none"})})}}).call(this); (function(){var n,t,a,e,r,i;r=function(){var e,r;return e=!1,$(".fabmenu").click(function(){return e?(a(),e=!1):(t(),e=!0)}),r=!1,$(".nav-img, .overlay, .nav-close").click(function(){return r?(n(),r=!1):(i(),r=!0)}),$(document).keyup(function(t){return r&&27===t.which?(n(),r=!1):r||77!==t.which?void 0:(i(),r=!0)}),$(".articletext a").attr("target","_blank")},$(document).ready(r),e=function(){return history.go(-1)},t=function(){return $(".subfab").fadeIn(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/close.svg"),$(".fab-img").fadeIn(60)})},a=function(){return $(".subfab").fadeOut(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/share.svg"),$(".fab-img").fadeIn(60)})},i=function(){return $(".nav").animate({left:"0px"},125),$(".overlay").show(),$(".overlay").animate({opacity:"0.4"},125)},n=function(){return $(".nav").animate({left:"-301px"},125),$(".overlay").animate({opacity:"0.0"},125,function(){return $(".overlay").hide})}}).call(this);

View file

@ -59,7 +59,7 @@ class ArticleGenerator
* *
* @return Null * @return Null
*/ */
function newArticle($directory, $articlefile, $blog, $excerpt, $readmore) function newArticle($directory, $articlefile, $blog)
{ {
$article = file_get_contents($directory . $articlefile); // get the file $article = file_get_contents($directory . $articlefile); // get the file
@ -96,39 +96,12 @@ class ArticleGenerator
echo "<div class='articletext'>"; echo "<div class='articletext'>";
// print only a excerpt of the post
// with at least 200 characters if possible.
if ( $excerpt == 'on' ) {
$emptyline = strpos($article, "\n\n");
if ( $emptyline !== false ) {
if ( $emptyline < 200) {
$emptyline2 = strpos($article, "\n\n", $emptyline);
if ( $emptyline2 !== false ) {
$article = substr($article, 0, $emptyline2);
} else {
$article = substr($article, 0, $emptyline);
}
} else {
$article = substr($article, 0, $emptyline);
}
} else {
// correct $excerpt for use in line 127.
$excerpt = 'off';
}
}
echo Parsedown::instance() echo Parsedown::instance()
->setBreaksEnabled(true) ->setBreaksEnabled(true)
->text($article); // print now the article text as html ->text($article); // print now the article text as html
echo "</div>"; echo "</div>";
if ( $excerpt == 'on' ) {
echo "<div class='readmore'><a href='$link'>$readmore</a></div>";
}
if (isset($author)) { if (isset($author)) {
echo "<span class='author'>$author</span>"; // print the author echo "<span class='author'>$author</span>"; // print the author
} }
@ -223,15 +196,15 @@ class ArticleGenerator
*/ */
static function getSummary($directory, $articlefile) static function getSummary($directory, $articlefile)
{ {
$text = ArticleGenerator::getText($directory, $articlefile); $text = getText($directory, $articlefile);
$pos = stripos($text, "\n\n"); $pos = stripos($text, ".");
if ($pos !== false) { if ($pos) {
$offset = $pos; $offset = $pos + 1;
$pos = stripos($text, ".", $offset); $pos = stripos($text, ".", $offset);
$summary = substr($text, 0, $pos) . "."; $summary = substr($text, 0, $pos) . ".";
return trim($summary); return $summary;
} else { } else {
return $text; return $text;
} }
@ -302,10 +275,6 @@ class ArticleGenerator
public function getArray($directory, $articlefile) public function getArray($directory, $articlefile)
{ {
$article = file_get_contents($directory . $articlefile); $article = file_get_contents($directory . $articlefile);
$title = "";
$date = "";
$author = "";
$tags = array();
if (substr($article, 0, 6) == "%TITLE") { // get and remove the title if (substr($article, 0, 6) == "%TITLE") { // get and remove the title
$title = substr($article, 8, strpos($article, "\n") - 8); $title = substr($article, 8, strpos($article, "\n") - 8);

View file

@ -10,7 +10,7 @@
* @license MIT License * @license MIT License
* @link http://marcel-kapfer.de/rangitaki * @link http://marcel-kapfer.de/rangitaki
*/ */
namespace mmk2410\rbe\BlogListGenerator;
/** /**
* The blog list generator class is a collection of functions for generating * The blog list generator class is a collection of functions for generating
@ -37,7 +37,7 @@ class BlogListGenerator
* *
* @return None * @return None
*/ */
public function listBlog($directory, $blogname, $blogmaintitle) function listBlog($directory, $blogname, $blogmaintitle)
{ {
// get content of the blog file; // get content of the blog file;
$blog = file_get_contents($directory . $blogname); $blog = file_get_contents($directory . $blogname);
@ -46,25 +46,18 @@ class BlogListGenerator
// check if the first line includes a title // check if the first line includes a title
if (substr($blog, 0, 6) == "%TITLE") { if (substr($blog, 0, 6) == "%TITLE") {
// grab the title // grab the title
$itemname = substr($blog, 8, strpos($blog, "\n") - 8); $blog = substr($blog, 8, strpos($blog, "\n") - 8);
// if on main blog // if on main blog
if ($itemname == "main") { if ($blog == "main") {
// create a nav item to the main blog // create a nav item to the main blog
$atag = "<a class='nav-item' href='./'>$blogmaintitle</a>"; echo "<a class='nav-item' href='./'>$blogmaintitle</a>";
} else { } else {
// create a link to the blog // create a link to the blog
$link = "./?blog=" . substr($blogname, 0, -3); $link = "./?blog=" . substr($blogname, 0, -3);
// create a nav item to the blog // create a nav item to the blog
$atag = "<a class='nav-item' href='$link'>$itemname</a>"; echo "<a class='nav-item' href='$link'>$blog</a>";
} }
$blog = substr($blog, strpos($blog, "\n") + 1);
} }
// nav item as link to external page
if (substr($blog, 0, 4) == "%URL") {
$itemurl = substr($blog, 6, strpos($blog, "\n") - 6);
$atag = "<a class='nav-item' href='$itemurl'>$itemname</a>";
}
return $atag;
} }
/** /**
@ -74,7 +67,7 @@ class BlogListGenerator
* *
* @return string * @return string
*/ */
public function getName($file) function getName($file)
{ {
// get the content of the blog file // get the content of the blog file
$blog = file_get_contents($file); $blog = file_get_contents($file);
@ -96,7 +89,7 @@ class BlogListGenerator
* *
* @return int Amount of files * @return int Amount of files
*/ */
public static function getArticleAmount($blog) static function getArticleAmount($blog)
{ {
$directory = "./articles/" . $blog . "/"; $directory = "./articles/" . $blog . "/";
if (!file_exists($directory)) { if (!file_exists($directory)) {
@ -104,7 +97,7 @@ class BlogListGenerator
} else { } else {
$i = 0; $i = 0;
$handle = opendir($directory); $handle = opendir($directory);
while (($file = readdir($handle)) !== false) { while (($file = readdir($handle)) !== false ) {
if (!in_array($file, array('.','..'))) { if (!in_array($file, array('.','..'))) {
$i++; $i++;
} }
@ -113,25 +106,4 @@ class BlogListGenerator
} }
} }
/**
* A function returning the external linkn of
* a blog.
*
* @param string $blog the blog name
* @param string $dir root directory of installation
*
* @return string link to external page else null
*/
public function getExternalLink($blog, $dir)
{
$path = $dir . "/blogs/" . $blog;
$blog = file_get_contents($path) . "\n";
if (substr($blog, 0, 6) == "%TITLE") {
$blog = substr($blog, strpos($blog, "\n") + 1);
}
if (substr($blog, 0, 4) == "%URL") {
return substr($blog, 6, strpos($blog, "\n") - 6);
}
return null;
}
} }

View file

@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
--> -->
<?php if ($config['blog']['disqus']) { // check if disqus is enabled ?> <?php if ($blogdisqus) { // check if disqus is enabled ?>
<section class="card"> <section class="card">
<div id="disqus_thread"></div> <div id="disqus_thread"></div>
<script type="text/javascript"> <script type="text/javascript">

View file

@ -11,7 +11,6 @@
* @link http://marcel-kapfer.de/rangitaki * @link http://marcel-kapfer.de/rangitaki
*/ */
require_once "BlogListGenerator.php"; require_once "BlogListGenerator.php";
use mmk2410\rbe\BlogListGenerator\BlogListGenerator as BlogListGenerator;
require_once './res/php/Config.php'; require_once './res/php/Config.php';

View file

@ -64,6 +64,9 @@ main = () ->
$(document).ready main $(document).ready main
goBack = () ->
history.go -1
fabFadeIn = () -> fabFadeIn = () ->
$('.subfab').fadeIn 125 $('.subfab').fadeIn 125
$('.fab-img').fadeOut 60, -> $('.fab-img').fadeOut 60, ->
@ -84,4 +87,5 @@ openNav = () ->
closeNav = () -> closeNav = () ->
$('.nav').animate {"left": "-301px"}, 125 $('.nav').animate {"left": "-301px"}, 125
$('.overlay').animate {"opacity": "0.0"}, 125, -> $('.overlay').animate {"opacity": "0.0"}, 125, ->
$('.overlay').css {"display": "none"} $('.overlay').hide

View file

@ -1,194 +0,0 @@
/*
*The MIT License
*
*Copyright 2015 mmk2410.
*
*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.
/*
* Created on : Jun 18, 2015, 6:39:37 PM
* Author : mmk2410
$font: "Fira Sans", sans-serif
$background: #f0f0f0
$text-color: #383838
$primary-color: #4CAF50
$header-shadow: rgba(62, 62, 62, 0.45)
$white: #FFFFFF
$fadeout-color: rgba(30, 87, 153, 0)
$border: #e0e0e0
$active-color: #e2e2e2
$fab-shadow: rgba(62, 62, 62, 0.3)
$footer-background: #2D2D2D
body
font-family: $font
background: $background
color: $text-color
margin: 130px 0 0
::selection, ::-moz-selection
color: $background
background-color: $primary-color
.header
height: 55px
background-color: $primary-color
position: fixed
box-shadow: 2px 0 2px 2px $header-shadow
.title
line-height: 55px
color: $white
position: absolute
left: 0
margin-left: calc(20% + 24px)
> a
color: $white
text-decoration: none
.fadeout
height: 55px
background: -moz-linear-gradient(left, $fadeout-color 0%, $primary-color 100%)
background: -webkit-linear-gradient(left, $fadeout-color 0%, $primary-color 100%)
background: -o-linear-gradient(left, $fadeout-color 0%, $primary-color 100%)
background: -ms-linear-gradient(left, $fadeout-color 0%, $primary-color 100%)
background: linear-gradient(to right, $fadeout-color 0%, $primary-color 100%)
.nav-img
padding: 14px 19px
.nav
background-color: $white
border-right: 1px solid $border
.nav-item, .nav-item-static
color: $text-color
.nav-item
font-weight: 600
&:hover
color: $primary-color
&:active
background-color: $active-color
.divider
border-bottom: 1px solid $border
.card
background: none
border-radius: 0
box-shadow: none
margin: 0 20% 100px
width: auto
a
color: $primary-color
text-decoration: none
border-bottom: 1px solid transparent
transition: border-bottom-color 150ms ease-in-out 100ms
&:hover
border-bottom-color: #4CAF50
.headline
font-size: 30px
line-height: 50px
color: #383838 !important
text-decoration: none
border-bottom: none !important
&:hover
color: #4CAF50 !important
.date
font-size: 13px
.articletext
margin-top: 30px
font-size: 16px
line-height: 30px
.author, .tag
font-size: 13px
.fab
background-color: $primary-color
box-shadow: 0 1px 1.5px 1.5px $fab-shadow
.subfab
background-color: $white
box-shadow: 0 1px 1.5px 1.5px $fab-shadow
.button
color: $primary-color
border-bottom: 1px solid transparent
margin: 0
min-width: 0
padding: 0
line-height: 16px
height: 16px
transition-property: border-bottom-color
transition-delay: 50ms
transition-duration: 125ms
transition-timing-function: ease
-moz-transition-property: border-bottom-color
-moz-transition-delay: 50ms
-moz-transition-duration: 125ms
-moz-transition-timing-function: ease
-webkit-transition-property: border-bottom-color
-webkit-transition-delay: 50ms
-webkit-transition-duration: 125ms
-webkit-transition-timing-function: ease
&:hover
border-bottom-color: $primary-color
.pag_prev
float: left
.button:last-child
float: none
.footer
text-align: center
height: 60px
background-color: $footer-background
line-height: 60px
width: 100%
color: $white
a
color: $white
text-decoration: none
border-bottom: 1px solid transparent
&:hover
border-bottom-color: $white
.hljs
background: none
@media screen and (min-width: 1440px)
.title
margin-left: calc(20% - 40px)
.nav
padding-top: 55px
@media screen and (max-width: 800px)
body
margin-top: 80px
.title
margin-left: 20%
.card
margin: 0 5% 100px

View file

@ -153,9 +153,6 @@ body
.tag .tag
.readmore
padding: 12px 0 25px 0
/* FAB */ /* FAB */
.fabmenu .fabmenu

View file

@ -1,91 +0,0 @@
<?php
require_once 'PHPUnit/Autoload.php';
include 'res/php/ArticleGenerator.php';
class ArticleGeneratorTest extends \PHPUnit_Framework_TestCase
{
public function testGetArray()
{
$result = [
"title" => "The Rangitaki logo 2",
"date" => "24 July 2015",
"tags" => array(
"design", "artwork", "logo",
),
"author" => "",
"text" => "
This is the official Rangitaki logo.
![The Rangitaki logo](media/example.png)
It is saved in the example blog directory.
",
];
$this->assertEquals(
$result,
ArticleGenerator::getArray("articles/example/", "2015-07-25-example.md")
);
}
public function testGetText()
{
$result = "
This is the official Rangitaki logo.
![The Rangitaki logo](media/example.png)
It is saved in the example blog directory.
";
$this->assertEquals(
$result,
ArticleGenerator::getText("articles/example/", "2015-07-25-example.md")
);
}
public function testGetAuthor()
{
$result = "";
$this->assertEquals(
$result,
ArticleGenerator::getAuthor("articles/example/", "2015-07-25-example.md")
);
}
public function testGetSummary()
{
$result = "This is the official Rangitaki logo.";
$this->assertEquals(
$result,
ArticleGenerator::getSummary("articles/example/", "2015-07-25-example.md")
);
}
public function testGetTags()
{
$result = [ "design", "artwork", "logo" ];
$this->assertEquals(
$result,
ArticleGenerator::getTags("articles/example/", "2015-07-25-example.md")
);
}
public function testGetDate()
{
$result = "24 July 2015";
$this->assertEquals(
$result,
ArticleGenerator::getDate("articles/example/", "2015-07-25-example.md")
);
}
public function testGetTitle()
{
$result = "The Rangitaki logo 2";
$this->assertEquals(
$result,
ArticleGenerator::getTitle("articles/example/", "2015-07-25-example.md")
);
}
}

View file

@ -1,39 +0,0 @@
<?php
use PHPUnit\Framework\TestCase;
require 'res/php/BlogListGenerator.php';
use mmk2410\rbe\BlogListGenerator\BlogListGenerator as BlogListGenerator;
class BlogListGeneratorTest extends TestCase
{
public function testListBlog()
{
$this->assertEquals("<a class='nav-item' href='./?blog=example'>Example</a>",
BlogListGenerator::listBlog("./blogs/", "example.md", "Example Blog")
);
$this->assertEquals("<a class='nav-item' href='https://mmk2410.org/rangitaki/docs/'>Docs</a>",
BlogListGenerator::listBlog("./blogs/", "external.md", "Example Blog")
);
}
public function testGetName()
{
$this->assertEquals("Example", BlogListGenerator::getName("blogs/example.md"));
}
public function testGetArticleAmount()
{
$this->assertEquals(5, BlogListGenerator::getArticleAmount("example"));
}
public function testGetExternaleLink()
{
$this->assertEquals(null,
BlogListGenerator::getExternalLink("example.md", '.')
);
$this->assertEquals("https://mmk2410.org/rangitaki/docs/",
BlogListGenerator::getExternalLink("external.md", '.')
);
}
}

View file

@ -1,83 +0,0 @@
<?php
namespace mmk2410\rbe\tests\config;
require_once 'PHPUnit/Autoload.php';
require 'res/php/Config.php';
use \mmk2410\rbe\config\Config as Config;
class ConfigTest extends \PHPUnit_Framework_TestCase
{
public function testGetConfig()
{
$config = [
"blog" => array(
"title" => "Example Blog",
"author" => "John",
"description" => "A short description of your blog",
"home" => "on",
"homeurl" => "../",
"homename" => "Home",
"mainname" => "",
"intro" => "on",
"disqus" => "rangitaki",
"analytics" => "",
"footer" =>
"Rangitaki 2016 <a href=\"https://gitlab.com/mmk2410/rangitaki\" target=\"blank\">\n gitlab.com/mmk2410/rangitaki</a>", "url" => "https://example.com/blog/",
),
"design" => array(
"fab" => "on",
"drawer" => "on",
"theme" => "material-light",
"pagination" => 0,
"favicon" => "http://example.com/res/img/favicon.png",
),
"rcc" => array(
"rcc" => "on",
"api" => "on",
),
"language" => "en",
];
$configParser = new Config("./config.yaml", "./vendor/autoload.php");
$this->assertEquals($config, $configParser->getConfig());
}
public function testWriteReadConfig()
{
$changedConfig = [
"blog" => array(
"title" => "Examples Blog",
"author" => "Wilson O'Sullivan",
"description" => "A long description of your blog",
"home" => "on",
"homeurl" => "../",
"homename" => "Exit",
"mainname" => "",
"intro" => "on",
"disqus" => "",
"analytics" => "",
"footer" =>
"pBlog 1102 <a href=\"https://gitlab.com/mmk2410/rangitaki\" target=\"blank\">
\n gitlab.com/mmk2410/rangitaki</a>", "url" => "https://example.com/blog/",
),
"design" => array(
"fab" => "off",
"drawer" => "off",
"theme" => "material-dark",
"pagination" => "-1",
"favicon" => "http://sample.com/res/img/favicon.png",
),
"rcc" => array(
"rcc" => "on",
"api" => "off",
),
"language" => "en",
];
$configParser = new Config("/tmp/config-test.yaml", "./vendor/autoload.php");
$configParser->writeConfig($changedConfig);
$this->assertEquals($changedConfig, $configParser->getConfig());
}
}

View file

@ -1,3 +0,0 @@
body{font-family:"Fira Sans",sans-serif;background:#f0f0f0;color:#383838;margin:130px 0 0}::selection,::-moz-selection{color:#f0f0f0;background-color:#4CAF50}.header{height:55px;background-color:#4CAF50;position:fixed;box-shadow:2px 0 2px 2px rgba(62,62,62,0.45)}.title{line-height:55px;color:#fff;position:absolute;left:0;margin-left:calc(20% + 24px)}.title>a{color:#fff;text-decoration:none}.fadeout{height:55px;background:-moz-linear-gradient(left, rgba(30,87,153,0) 0%, #4CAF50 100%);background:-webkit-linear-gradient(left, rgba(30,87,153,0) 0%, #4CAF50 100%);background:-o-linear-gradient(left, rgba(30,87,153,0) 0%, #4CAF50 100%);background:-ms-linear-gradient(left, rgba(30,87,153,0) 0%, #4CAF50 100%);background:linear-gradient(to right, rgba(30,87,153,0) 0%, #4CAF50 100%)}.nav-img{padding:14px 19px}.nav{background-color:#fff;border-right:1px solid #e0e0e0}.nav-item,.nav-item-static{color:#383838}.nav-item{font-weight:600}.nav-item:hover{color:#4CAF50}.nav-item:active{background-color:#e2e2e2}.divider{border-bottom:1px solid #e0e0e0}.card{background:none;border-radius:0;box-shadow:none;margin:0 20% 100px;width:auto}.card a{color:#4CAF50;text-decoration:none;border-bottom:1px solid transparent;transition:border-bottom-color 150ms ease-in-out 100ms}.card a:hover{border-bottom-color:#4CAF50}.headline{font-size:30px;line-height:50px;color:#383838 !important;text-decoration:none;border-bottom:none !important}.headline:hover{color:#4CAF50 !important}.date{font-size:13px}.articletext{margin-top:30px;font-size:16px;line-height:30px}.author,.tag{font-size:13px}.fab{background-color:#4CAF50;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.subfab{background-color:#fff;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.button{color:#4CAF50;border-bottom:1px solid transparent;margin:0;min-width:0;padding:0;line-height:16px;height:16px;transition-property:border-bottom-color;transition-delay:50ms;transition-duration:125ms;transition-timing-function:ease;-moz-transition-property:border-bottom-color;-moz-transition-delay:50ms;-moz-transition-duration:125ms;-moz-transition-timing-function:ease;-webkit-transition-property:border-bottom-color;-webkit-transition-delay:50ms;-webkit-transition-duration:125ms;-webkit-transition-timing-function:ease}.button:hover{border-bottom-color:#4CAF50}.pag_prev{float:left}.button:last-child{float:none}.footer{text-align:center;height:60px;background-color:#2D2D2D;line-height:60px;width:100%;color:#fff}.footer a{color:#fff;text-decoration:none;border-bottom:1px solid transparent}.footer a:hover{border-bottom-color:#fff}.hljs{background:none}@media screen and (min-width: 1440px){.title{margin-left:calc(20% - 40px)}.nav{padding-top:55px}}@media screen and (max-width: 800px){body{margin-top:80px}.title{margin-left:20%}.card{margin:0 5% 100px}}
/*# sourceMappingURL=nextDESIGN.css.map */

File diff suppressed because one or more lines are too long

View file

@ -1,70 +0,0 @@
#!/bin/bash
# Update script for Rangitaki from version 1.4.0 to 1.4.1
version="1.4.1"
new="./rbe-new"
echo -n "Downloading version $version from GitLab... "
git clone -q https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout master
cd ../
fi
echo "done"
echo -n "Updating ressources... "
rm -rf ./res/
mv $new/res/ ./
echo "done"
echo -n "Updating binaries... "
rm -rf ./bin
mv $new/bin/ ./
echo "done"
echo -n "Updating source files... "
rm -rf ./src
mv $new/src/ ./
echo "done"
echo -n "Updating RCC... "
rm -rf ./rcc
mv $new/rcc ./
rm ./rcc/password.php
echo "done"
echo -n "Updating core... "
rm ./index.php
mv $new/index.php ./
echo "done"
echo -n "Updating npm... "
mv $new/package.json ./
echo "done"
echo -n "Updating Changelog... "
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "done"
if [[ $1 != "--debug" ]]; then
echo -n "Cleaning up... "
rm -rf $new
echo "done"
fi
if [ -d "./update-scripts" ]; then
echo -n "Remove obsolete update scripts folder... "
rm -rf "./update-scripts"
echo "done"
fi
echo "$version" > ./VERSION
echo "Your Rangitaki installation is updated to version $version"

View file

@ -1,48 +0,0 @@
#!/bin/bash
# Update script for Rangitaki from version 1.4.0 to 1.4.1
version="1.4.2"
new="./rbe-new"
echo -n "Downloading version $version from GitLab... "
git clone -q https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout master
cd ../
fi
echo "done"
echo -n "Updating RCC... "
mv ./rcc/password.php ./
rm -rf ./rcc
mv $new/rcc ./
rm ./rcc/password.php
mv ./password.php ./rcc/
echo "done"
echo -n "Updating Changelog... "
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "done"
if [[ $1 != "--debug" ]]; then
echo -n "Cleaning up... "
rm -rf $new
echo "done"
fi
if [ -d "./update-scripts" ]; then
echo -n "Remove obsolete update scripts folder... "
rm -rf "./update-scripts"
echo "done"
fi
echo "$version" > ./VERSION
echo "Your Rangitaki installation is updated to version $version"

View file

@ -1,78 +0,0 @@
#!/bin/bash
# Update script for Rangitaki from version 1.4.2 to 1.4.3
# Also works from 1.4.0 to 1.4.3
version="1.4.3"
new="./rbe-new"
echo -n "Downloading version $version from GitLab... "
git clone -q https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout -q master
cd ../
fi
echo "done"
echo -n "Updating ressources... "
rm -rf ./res/
mv $new/res/ ./
echo "done"
echo -n "Updating source files... "
rm ./src/coffee/app.coffee
mv $new/src/coffee/app.coffee ./src/coffee/
mv $new/src/sass-themes/nextDESIGN.sass ./src/sass-themes/
echo "done"
echo -n "Updating RCC... "
rm -rf ./rcc
mv $new/rcc ./
rm ./rcc/password.php
echo "done"
echo -n "Updating core... "
rm ./index.php
mv $new/index.php ./
echo "done"
echo -n "Updating binaries... "
rm -rf ./bin
mv $new/bin/ ./
echo "done"
echo -n "Updating themes... "
rm ./themes/material-light.css*
rm ./themes/material-dark.css*
rm ./themes/background-img.css*
mv $new/themes/* ./themes/
echo "done"
echo -n "Updating npm... "
mv $new/package.json ./
echo "done"
echo -n "Updating Changelog... "
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "done"
echo -n "Cleaning up... "
if [[ $1 != "--debug" ]]; then
echo -n "Cleaning up... "
rm -rf $new
echo "done"
fi
if [ -d "./update-scripts" ]; then
echo -n "Remove obsolete update scripts folder... "
rm -rf "./update-scripts"
echo "done"
fi
echo "Your Rangitaki installation is updated to version $version"

View file

@ -1,51 +0,0 @@
#!/bin/bash
# Update script for Rangitaki from version 1.4.3 to 1.4.4
version="1.4.4"
new="./rbe-new"
echo -n "Downloading version $version from GitLab... "
git clone -q https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout -q master
cd ../
fi
echo "done"
echo -n "Updating RCC... "
mv ./rcc/password.php ./password.php
rm -rf ./rcc/
mv $new/rcc ./
rm ./rcc/password.php
mv ./password.php ./rcc/password.php
echo "done"
echo -n "Updating npm... "
mv $new/package.json ./
echo "done"
echo -n "Updating Changelog... "
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "done"
echo -n "Cleaning up... "
if [[ $1 != "--debug" ]]; then
echo -n "Cleaning up... "
rm -rf $new
echo "done"
fi
if [ -d "./update-scripts" ]; then
echo -n "Remove obsolete update scripts folder... "
rm -rf "./update-scripts"
echo "done"
fi
echo "Your Rangitaki installation is updated to version $version"

View file

@ -1,84 +0,0 @@
#!/bin/bash
# Update script for Rangitaki from version 1.4.4 to 1.5.0
version="1.5.0"
new="./rbe-new"
echo -n "Downloading version $version from GitLab... "
git clone -q https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout -q master
cd ../
fi
echo "done"
echo -n "Updating ressources... "
rm -rf ./res/
mv $new/res/ ./
echo "done"
echo -n "Updating languages... "
rm -rf ./lang/de.php
rm -rf ./lang/en.php
mv $new/lang/* ./lang/
echo "done"
echo -n "Updating source files... "
rm ./src/sass/rangitaki.sass
mv $new/src/sass/rangitaki.sass ./src/sass/
echo "done"
echo -n "Updating core... "
rm ./index.php
mv $new/index.php ./
echo "done"
echo -n "Updating binaries... "
rm -rf ./bin
mv $new/bin/ ./
echo "done"
echo -n "Updating themes... "
rm ./themes/material-light.css*
rm ./themes/material-dark.css*
rm ./themes/background-img.css*
rm ./themes/nextDESIGN.css*
mv $new/themes/* ./themes/
echo "done"
echo -n "Updating npm... "
mv $new/package.json ./
echo "done"
echo -n 'Updating config script... '
echo "social:" >> ./config.yaml
echo " twitter: ''" >> ./config.yaml
sed -i "s/design:/design:\n excerpt: 'off'/" config.yaml
echo "done"
echo -n "Updating Changelog... "
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "done"
echo -n "Cleaning up... "
if [[ $1 != "--debug" ]]; then
echo -n "Cleaning up... "
rm -rf $new
echo "done"
fi
if [ -d "./update-scripts" ]; then
echo -n "Remove obsolete update scripts folder... "
rm -rf "./update-scripts"
echo "done"
fi
echo "Your Rangitaki installation is updated to version $version"
echo "Run php ./bin/init.php to use set the values for the new features."

2
vendor/autoload.php vendored
View file

@ -2,6 +2,6 @@
// autoload.php @generated by Composer // autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php'; require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitd1db2574a85c0ba6f142743249ba228f::getLoader(); return ComposerAutoloaderInitd1db2574a85c0ba6f142743249ba228f::getLoader();

View file

@ -0,0 +1,5 @@
# Test Files #
test/config/test.sqlite
vendor
composer.lock
.idea

View file

@ -0,0 +1,25 @@
language: php
sudo: false
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
env:
global:
- SKIP_MONGO_TESTS=1
- secure: Bc5ZqvZ1YYpoPZNNuU2eCB8DS6vBYrAdfBtTenBs5NSxzb+Vjven4kWakbzaMvZjb/Ib7Uph7DGuOtJXpmxnvBXPLd707LZ89oFWN/yqQlZKCcm8iErvJCB5XL+/ONHj2iPdR242HJweMcat6bMCwbVWoNDidjtWMH0U2mYFy3M=
- secure: R3bXlymyFiY2k2jf7+fv/J8i34wtXTkmD4mCr5Ps/U+vn9axm2VtvR2Nj+r7LbRjn61gzFE/xIVjYft/wOyBOYwysrfriydrnRVS0owh6y+7EyOyQWbRX11vVQMf8o31QCQE5BY58V5AJZW3MjoOL0FVlTgySJiJvdw6Pv18v+E=
services:
- mongodb
- redis-server
- cassandra
before_script:
- psql -c 'create database oauth2_server_php;' -U postgres
- composer require predis/predis:dev-master
- composer require thobbs/phpcassa:dev-master
- composer require 'aws/aws-sdk-php:~2.8'
- composer require 'firebase/php-jwt:~2.2'
after_script:
- php test/cleanup.php

View file

@ -0,0 +1,165 @@
CHANGELOG for 1.x
=================
This changelog references the relevant changes (bug and security fixes) done
in 1.x minor versions.
To see the files changed for a given bug, go to https://github.com/bshaffer/oauth2-server-php/issues/### where ### is the bug number
To get the diff between two versions, go to https://github.com/bshaffer/oauth2-server-php/compare/v1.0...v1.1
To get the diff for a specific change, go to https://github.com/bshaffer/oauth2-server-php/commit/XXX where XXX is the change hash
* 1.8.0 (2015-09-18)
PR: https://github.com/bshaffer/oauth2-server-php/pull/643
* bug #594 - adds jti
* bug #598 - fixes lifetime configurations for JWTs
* bug #634 - fixes travis builds, upgrade to containers
* bug #586 - support for revoking tokens
* bug #636 - Adds FirebaseJWT bridge
* bug #639 - Mongo HHVM compatibility
* 1.7.0 (2015-04-23)
PR: https://github.com/bshaffer/oauth2-server-php/pull/572
* bug #500 - PDO fetch mode changed from FETCH_BOTH to FETCH_ASSOC
* bug #508 - Case insensitive for Bearer token header name ba716d4
* bug #512 - validateRedirectUri is now public
* bug #530 - Add PublicKeyInterface, UserClaimsInterface to Cassandra Storage
* bug #505 - DynamoDB storage fixes
* bug #556 - adds "code id_token" return type to openid connect
* bug #563 - Include "issuer" config key for JwtAccessToken
* bug #564 - Fixes JWT vulnerability
* bug #571 - Added unset_refresh_token_after_use option
* 1.6 (2015-01-16)
PR: https://github.com/bshaffer/oauth2-server-php/pull/496
* bug 437 - renames CryptoToken to JwtAccessToken / use_crypto_tokens to use_jwt_access_tokens
* bug 447 - Adds a Couchbase storage implementation
* bug 460 - Rename JWT claims to match spec
* bug 470 - order does not matter for multi-valued response types
* bug 471 - Make validateAuthorizeRequest available for POST in addition to GET
* bug 475 - Adds JTI table definitiion
* bug 481 - better randomness for generating access tokens
* bug 480 - Use hash_equals() for signature verification (prevents remote timing attacks)
* bugs 489, 491, 498 - misc other fixes
* 1.5 (2014-08-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/446
* bug #399 - Add DynamoDB Support
* bug #404 - renamed error name for malformed/expired tokens
* bug #412 - Openid connect: fixes for claims with more than one scope / Add support for the prompt parameter ('consent' and 'none')
* bug #411 - fixes xml output
* bug #413 - fixes invalid format error
* bug #401 - fixes code standards / whitespace
* bug #354 - bundles PDO SQL with the library
* [BC] bug #397 - refresh tokens should not be encrypted
* bug #423 - makes "scope" optional for refresh token storage
* 1.4 (2014-06-12)
PR: https://github.com/bshaffer/oauth2-server-php/pull/392
* bug #189 Storage\PDO - allows DSN string in constructor
* bug #233 Bearer Tokens - allows token in request body for PUT requests
* bug #346 Fixes open_basedir warning
* bug #351 Adds OpenID Connect support
* bug #355 Adds php 5.6 and HHVM to travis.ci testing
* [BC] bug #358 Adds `getQuerystringIdentifier()` to the GrantType interface
* bug #363 Encryption\JWT - Allows for subclassing JWT Headers
* bug #349 Bearer Tokens - adds requestHasToken method for when access tokens are optional
* bug #301 Encryption\JWT - fixes urlSafeB64Encode(): ensures newlines are replaced as expected
* bug #323 ResourceController - client_id is no longer required to be returned when calling getAccessToken
* bug #367 Storage\PDO - adds Postgres support
* bug #368 Access Tokens - use mcrypt_create_iv or openssl_random_pseudo_bytes to create token string
* bug #376 Request - allows case insensitive headers
* bug #384 Storage\PDO - can pass in PDO options in constructor of PDO storage
* misc fixes #361, #292, #373, #374, #379, #396
* 1.3 (2014-02-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/325
* bug #311 adds cassandra storage
* bug #298 fixes response code for user credentials grant type
* bug #318 adds 'use_crypto_tokens' config to Server class for better DX
* [BC] bug #320 pass client_id to getDefaultScope
* bug #324 better feedback when running tests
* bug #335 adds support for non-expiring refresh tokens
* bug #333 fixes Pdo storage for getClientKey
* bug #336 fixes Redis storage for expireAuthorizationCode
* 1.3 (2014-02-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/325
* bug #311 adds cassandra storage
* bug #298 fixes response code for user credentials grant type
* bug #318 adds 'use_crypto_tokens' config to Server class for better DX
* bug #320 pass client_id to getDefaultScope
* bug #324 better feedback when running tests
* bug #335 adds support for non-expiring refresh tokens
* bug #333 fixes Pdo storage for getClientKey
* bug #336 fixes Redis storage for expireAuthorizationCode
* 1.2 (2014-01-03)
PR: https://github.com/bshaffer/oauth2-server-php/pull/288
* bug #285 changed response header from 200 to 401 when empty token received
* bug #286 adds documentation and links to spec for not including error messages when no token is supplied
* bug #280 ensures PHP warnings do not get thrown as a result of an invalid argument to $jwt->decode()
* bug #279 predis wrong number of arguments
* bug #277 Securing JS WebApp client secret w/ password grant type
* 1.1 (2013-12-17)
PR: https://github.com/bshaffer/oauth2-server-php/pull/276
* bug #278 adds refresh token configuration to Server class
* bug #274 Supplying a null client_id and client_secret grants API access
* bug #244 [MongoStorage] More detailed implementation info
* bug #268 Implement jti for JWT Bearer tokens to prevent replay attacks.
* bug #266 Removing unused argument to getAccessTokenData
* bug #247 Make Bearer token type consistent
* bug #253 Fixing CryptoToken refresh token lifetime
* bug #246 refactors public key logic to be more intuitive
* bug #245 adds support for JSON crypto tokens
* bug #230 Remove unused columns in oauth_clients
* bug #215 makes Redis Scope Storage obey the same paradigm as PDO
* bug #228 removes scope group
* bug #227 squelches open basedir restriction error
* bug #223 Updated docblocks for RefreshTokenInterface.php
* bug #224 Adds protected properties
* bug #217 Implement ScopeInterface for PDO, Redis
* 1.0 (2013-08-12)
* bug #203 Add redirect\_status_code config param for AuthorizeController
* bug #205 ensures unnecessary ? is not set when ** bug
* bug #204 Fixed call to LogicException
* bug #202 Add explode to checkRestrictedGrant in PDO Storage
* bug #197 adds support for 'false' default scope ** bug
* bug #192 reference errors and adds tests
* bug #194 makes some appropriate properties ** bug
* bug #191 passes config to HttpBasic
* bug #190 validates client credentials before ** bug
* bug #171 Fix wrong redirect following authorization step
* bug #187 client_id is now passed to getDefaultScope().
* bug #176 Require refresh_token in getRefreshToken response
* bug #174 make user\_id not required for refresh_token grant
* bug #173 Duplication in JwtBearer Grant
* bug #168 user\_id not required for authorization_code grant
* bug #133 hardens default security for user object
* bug #163 allows redirect\_uri on authorization_code to be NULL in docs example
* bug #162 adds getToken on ResourceController for convenience
* bug #161 fixes fatal error
* bug #163 Invalid redirect_uri handling
* bug #156 user\_id in OAuth2\_Storage_AuthorizationCodeInterface::getAuthorizationCode() response
* bug #157 Fix for extending access and refresh tokens
* bug #154 ResponseInterface: getParameter method is used in the library but not defined in the interface
* bug #148 Add more detail to examples in Readme.md

View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2014 Brent Shaffer
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.

View file

@ -0,0 +1,8 @@
oauth2-server-php
=================
[![Build Status](https://travis-ci.org/bshaffer/oauth2-server-php.svg?branch=develop)](https://travis-ci.org/bshaffer/oauth2-server-php)
[![Total Downloads](https://poser.pugx.org/bshaffer/oauth2-server-php/downloads.png)](https://packagist.org/packages/bshaffer/oauth2-server-php)
View the [complete documentation](http://bshaffer.github.io/oauth2-server-php-docs/)

View file

@ -0,0 +1,27 @@
{
"name": "bshaffer/oauth2-server-php",
"description":"OAuth2 Server for PHP",
"keywords":["oauth","oauth2","auth"],
"type":"library",
"license":"MIT",
"authors":[
{
"name":"Brent Shaffer",
"email": "bshafs@gmail.com",
"homepage":"http://brentertainment.com"
}
],
"homepage": "http://github.com/bshaffer/oauth2-server-php",
"require":{
"php":">=5.3.9"
},
"autoload": {
"psr-0": { "OAuth2": "src/" }
},
"suggest": {
"predis/predis": "Required to use the Redis storage engine",
"thobbs/phpcassa": "Required to use the Cassandra storage engine",
"aws/aws-sdk-php": "~2.8 is required to use the DynamoDB storage engine",
"firebase/php-jwt": "~2.2 is required to use JWT features"
}
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Oauth2 Test Suite">
<directory>./test/OAuth2/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/OAuth2/</directory>
</whitelist>
</filter>
</phpunit>

View file

@ -0,0 +1,48 @@
<?php
namespace OAuth2;
/**
* Autoloads OAuth2 classes
*
* @author Brent Shaffer <bshafs at gmail dot com>
* @license MIT License
*/
class Autoloader
{
private $dir;
public function __construct($dir = null)
{
if (is_null($dir)) {
$dir = dirname(__FILE__).'/..';
}
$this->dir = $dir;
}
/**
* Registers OAuth2\Autoloader as an SPL autoloader.
*/
public static function register($dir = null)
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(new self($dir), 'autoload'));
}
/**
* Handles autoloading of classes.
*
* @param string $class A class name.
*
* @return boolean Returns true if the class has been loaded
*/
public function autoload($class)
{
if (0 !== strpos($class, 'OAuth2')) {
return;
}
if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) {
require $file;
}
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace OAuth2\ClientAssertionType;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Interface for all OAuth2 Client Assertion Types
*/
interface ClientAssertionTypeInterface
{
public function validateRequest(RequestInterface $request, ResponseInterface $response);
public function getClientId();
}

View file

@ -0,0 +1,123 @@
<?php
namespace OAuth2\ClientAssertionType;
use OAuth2\Storage\ClientCredentialsInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Validate a client via Http Basic authentication
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class HttpBasic implements ClientAssertionTypeInterface
{
private $clientData;
protected $storage;
protected $config;
/**
* @param OAuth2\Storage\ClientCredentialsInterface $clientStorage REQUIRED Storage class for retrieving client credentials information
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
* 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
* );
* </code>
*/
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
$this->storage = $storage;
$this->config = array_merge(array(
'allow_credentials_in_request_body' => true,
'allow_public_clients' => true,
), $config);
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$clientData = $this->getClientCredentials($request, $response)) {
return false;
}
if (!isset($clientData['client_id'])) {
throw new \LogicException('the clientData array must have "client_id" set');
}
if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
if (!$this->config['allow_public_clients']) {
$response->setError(400, 'invalid_client', 'client credentials are required');
return false;
}
if (!$this->storage->isPublicClient($clientData['client_id'])) {
$response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret');
return false;
}
} elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
$response->setError(400, 'invalid_client', 'The client credentials are invalid');
return false;
}
$this->clientData = $clientData;
return true;
}
public function getClientId()
{
return $this->clientData['client_id'];
}
/**
* Internal function used to get the client credentials from HTTP basic
* auth or POST data.
*
* According to the spec (draft 20), the client_id can be provided in
* the Basic Authorization header (recommended) or via GET/POST.
*
* @return
* A list containing the client identifier and password, for example
* @code
* return array(
* "client_id" => CLIENT_ID, // REQUIRED the client id
* "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
* );
* @endcode
*
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*
* @ingroup oauth2_section_2
*/
public function getClientCredentials(RequestInterface $request, ResponseInterface $response = null)
{
if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) {
return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW'));
}
if ($this->config['allow_credentials_in_request_body']) {
// Using POST for HttpBasic authorization is not recommended, but is supported by specification
if (!is_null($request->request('client_id'))) {
/**
* client_secret can be null if the client's password is an empty string
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*/
return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
}
}
if ($response) {
$message = $this->config['allow_credentials_in_request_body'] ? ' or body' : '';
$response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message);
}
return null;
}
}

View file

@ -0,0 +1,383 @@
<?php
namespace OAuth2\Controller;
use OAuth2\Storage\ClientInterface;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
use OAuth2\Scope;
/**
* @see OAuth2\Controller\AuthorizeControllerInterface
*/
class AuthorizeController implements AuthorizeControllerInterface
{
private $scope;
private $state;
private $client_id;
private $redirect_uri;
private $response_type;
protected $clientStorage;
protected $responseTypes;
protected $config;
protected $scopeUtil;
/**
* @param OAuth2\Storage\ClientInterface $clientStorage REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information
* @param array $responseTypes OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array
* keys are "code" and "token"
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'allow_implicit' => false, // if the controller should allow the "implicit" grant type
* 'enforce_state' => true // if the controller should require the "state" parameter
* 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter
* 'redirect_status_code' => 302, // HTTP status code to use for redirect responses
* );
* </code>
* @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
*/
public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null)
{
$this->clientStorage = $clientStorage;
$this->responseTypes = $responseTypes;
$this->config = array_merge(array(
'allow_implicit' => false,
'enforce_state' => true,
'require_exact_redirect_uri' => true,
'redirect_status_code' => 302,
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
{
if (!is_bool($is_authorized)) {
throw new \InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.');
}
// We repeat this, because we need to re-validate. The request could be POSTed
// by a 3rd-party (because we are not internally enforcing NONCEs, etc)
if (!$this->validateAuthorizeRequest($request, $response)) {
return;
}
// If no redirect_uri is passed in the request, use client's registered one
if (empty($this->redirect_uri)) {
$clientData = $this->clientStorage->getClientDetails($this->client_id);
$registered_redirect_uri = $clientData['redirect_uri'];
}
// the user declined access to the client's application
if ($is_authorized === false) {
$redirect_uri = $this->redirect_uri ?: $registered_redirect_uri;
$this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id);
return;
}
// build the parameters to set in the redirect URI
if (!$params = $this->buildAuthorizeParameters($request, $response, $user_id)) {
return;
}
$authResult = $this->responseTypes[$this->response_type]->getAuthorizeResponse($params, $user_id);
list($redirect_uri, $uri_params) = $authResult;
if (empty($redirect_uri) && !empty($registered_redirect_uri)) {
$redirect_uri = $registered_redirect_uri;
}
$uri = $this->buildUri($redirect_uri, $uri_params);
// return redirect response
$response->setRedirect($this->config['redirect_status_code'], $uri);
}
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
{
$error = 'access_denied';
$error_message = 'The user denied access to your application';
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message);
}
/*
* We have made this protected so this class can be extended to add/modify
* these parameters
*/
protected function buildAuthorizeParameters($request, $response, $user_id)
{
// @TODO: we should be explicit with this in the future
$params = array(
'scope' => $this->scope,
'state' => $this->state,
'client_id' => $this->client_id,
'redirect_uri' => $this->redirect_uri,
'response_type' => $this->response_type,
);
return $params;
}
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
{
// Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI)
if (!$client_id = $request->query('client_id', $request->request('client_id'))) {
// We don't have a good URI to use
$response->setError(400, 'invalid_client', "No client id supplied");
return false;
}
// Get client details
if (!$clientData = $this->clientStorage->getClientDetails($client_id)) {
$response->setError(400, 'invalid_client', 'The client id supplied is invalid');
return false;
}
$registered_redirect_uri = isset($clientData['redirect_uri']) ? $clientData['redirect_uri'] : '';
// Make sure a valid redirect_uri was supplied. If specified, it must match the clientData URI.
// @see http://tools.ietf.org/html/rfc6749#section-3.1.2
// @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1
// @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1
if ($supplied_redirect_uri = $request->query('redirect_uri', $request->request('redirect_uri'))) {
// validate there is no fragment supplied
$parts = parse_url($supplied_redirect_uri);
if (isset($parts['fragment']) && $parts['fragment']) {
$response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment');
return false;
}
// validate against the registered redirect uri(s) if available
if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2');
return false;
}
$redirect_uri = $supplied_redirect_uri;
} else {
// use the registered redirect_uri if none has been supplied, if possible
if (!$registered_redirect_uri) {
$response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored');
return false;
}
if (count(explode(' ', $registered_redirect_uri)) > 1) {
$response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3');
return false;
}
$redirect_uri = $registered_redirect_uri;
}
// Select the redirect URI
$response_type = $request->query('response_type', $request->request('response_type'));
// for multiple-valued response types - make them alphabetical
if (false !== strpos($response_type, ' ')) {
$types = explode(' ', $response_type);
sort($types);
$response_type = ltrim(implode(' ', $types));
}
$state = $request->query('state', $request->request('state'));
// type and client_id are required
if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null);
return false;
}
if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
if (!isset($this->responseTypes['code'])) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null);
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
return false;
}
if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied');
return false;
}
} else {
if (!$this->config['allow_implicit']) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null);
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
return false;
}
}
// validate requested scope if it exists
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
if ($requestedScope) {
// restrict scope by client specific scope if applicable,
// otherwise verify the scope exists
$clientScope = $this->clientStorage->getClientScope($client_id);
if ((is_null($clientScope) && !$this->scopeUtil->scopeExists($requestedScope))
|| ($clientScope && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null);
return false;
}
} else {
// use a globally-defined default scope
$defaultScope = $this->scopeUtil->getDefaultScope($client_id);
if (false === $defaultScope) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null);
return false;
}
$requestedScope = $defaultScope;
}
// Validate state parameter exists (if configured to enforce this)
if ($this->config['enforce_state'] && !$state) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required');
return false;
}
// save the input data and return true
$this->scope = $requestedScope;
$this->state = $state;
$this->client_id = $client_id;
// Only save the SUPPLIED redirect URI (@see http://tools.ietf.org/html/rfc6749#section-4.1.3)
$this->redirect_uri = $supplied_redirect_uri;
$this->response_type = $response_type;
return true;
}
/**
* Build the absolute URI based on supplied URI and parameters.
*
* @param $uri An absolute URI.
* @param $params Parameters to be append as GET.
*
* @return
* An absolute URI with supplied parameters.
*
* @ingroup oauth2_section_4
*/
private function buildUri($uri, $params)
{
$parse_url = parse_url($uri);
// Add our params to the parsed uri
foreach ($params as $k => $v) {
if (isset($parse_url[$k])) {
$parse_url[$k] .= "&" . http_build_query($v, '', '&');
} else {
$parse_url[$k] = http_build_query($v, '', '&');
}
}
// Put humpty dumpty back together
return
((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
. ((isset($parse_url["user"])) ? $parse_url["user"]
. ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
. ((isset($parse_url["query"]) && !empty($parse_url['query'])) ? "?" . $parse_url["query"] : "")
. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "")
;
}
protected function getValidResponseTypes()
{
return array(
self::RESPONSE_TYPE_ACCESS_TOKEN,
self::RESPONSE_TYPE_AUTHORIZATION_CODE,
);
}
/**
* Internal method for validating redirect URI supplied
*
* @param string $inputUri The submitted URI to be validated
* @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to
* allow for multiple URIs
*
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
*/
protected function validateRedirectUri($inputUri, $registeredUriString)
{
if (!$inputUri || !$registeredUriString) {
return false; // if either one is missing, assume INVALID
}
$registered_uris = preg_split('/\s+/', $registeredUriString);
foreach ($registered_uris as $registered_uri) {
if ($this->config['require_exact_redirect_uri']) {
// the input uri is validated against the registered uri using exact match
if (strcmp($inputUri, $registered_uri) === 0) {
return true;
}
} else {
// the input uri is validated against the registered uri using case-insensitive match of the initial string
// i.e. additional query parameters may be applied
if (strcasecmp(substr($inputUri, 0, strlen($registered_uri)), $registered_uri) === 0) {
return true;
}
}
}
return false;
}
/**
* Convenience methods to access the parameters derived from the validated request
*/
public function getScope()
{
return $this->scope;
}
public function getState()
{
return $this->state;
}
public function getClientId()
{
return $this->client_id;
}
public function getRedirectUri()
{
return $this->redirect_uri;
}
public function getResponseType()
{
return $this->response_type;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a user should be authorized
* by an authorization server. As OAuth2 does not handle
* authorization directly, this controller ensures the request is valid, but
* requires the application to determine the value of $is_authorized
*
* ex:
* > $user_id = $this->somehowDetermineUserId();
* > $is_authorized = $this->somehowDetermineUserAuthorization();
* > $response = new OAuth2\Response();
* > $authorizeController->handleAuthorizeRequest(
* > OAuth2\Request::createFromGlobals(),
* > $response,
* > $is_authorized,
* > $user_id);
* > $response->send();
*
*/
interface AuthorizeControllerInterface
{
/**
* List of possible authentication response types.
* The "authorization_code" mechanism exclusively supports 'code'
* and the "implicit" mechanism exclusively supports 'token'.
*
* @var string
* @see http://tools.ietf.org/html/rfc6749#section-4.1.1
* @see http://tools.ietf.org/html/rfc6749#section-4.2.1
*/
const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code';
const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
}

View file

@ -0,0 +1,111 @@
<?php
namespace OAuth2\Controller;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\Storage\AccessTokenInterface;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
use OAuth2\Scope;
/**
* @see OAuth2\Controller\ResourceControllerInterface
*/
class ResourceController implements ResourceControllerInterface
{
private $token;
protected $tokenType;
protected $tokenStorage;
protected $config;
protected $scopeUtil;
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ScopeInterface $scopeUtil = null)
{
$this->tokenType = $tokenType;
$this->tokenStorage = $tokenStorage;
$this->config = array_merge(array(
'www_realm' => 'Service',
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
{
$token = $this->getAccessTokenData($request, $response);
// Check if we have token data
if (is_null($token)) {
return false;
}
/**
* Check scope, if provided
* If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
* @see http://tools.ietf.org/html/rfc6750#section-3.1
*/
if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
$response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
$response->addHttpHeaders(array(
'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
$this->tokenType->getTokenType(),
$this->config['www_realm'],
$scope,
$response->getParameter('error'),
$response->getParameter('error_description')
)
));
return false;
}
// allow retrieval of the token
$this->token = $token;
return (bool) $token;
}
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
{
// Get the token parameter
if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) {
// Get the stored token data (from the implementing subclass)
// Check we have a well formed token
// Check token expiration (expires is a mandatory paramter)
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
} elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
$response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
} elseif (time() > $token["expires"]) {
$response->setError(401, 'expired_token', 'The access token provided has expired');
} else {
return $token;
}
}
$authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']);
if ($error = $response->getParameter('error')) {
$authHeader = sprintf('%s, error="%s"', $authHeader, $error);
if ($error_description = $response->getParameter('error_description')) {
$authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description);
}
}
$response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));
return null;
}
// convenience method to allow retrieval of the token
public function getToken()
{
return $this->token;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a "resource" is requested.
* call verifyResourceRequest in order to determine if the request
* contains a valid token.
*
* ex:
* > if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
* > $response->send(); // authorization failed
* > die();
* > }
* > return json_encode($resource); // valid token! Send the stuff!
*
*/
interface ResourceControllerInterface
{
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
}

View file

@ -0,0 +1,274 @@
<?php
namespace OAuth2\Controller;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\GrantType\GrantTypeInterface;
use OAuth2\ScopeInterface;
use OAuth2\Scope;
use OAuth2\Storage\ClientInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\TokenControllerInterface
*/
class TokenController implements TokenControllerInterface
{
protected $accessToken;
protected $grantTypes;
protected $clientAssertionType;
protected $scopeUtil;
protected $clientStorage;
public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null)
{
if (is_null($clientAssertionType)) {
foreach ($grantTypes as $grantType) {
if (!$grantType instanceof ClientAssertionTypeInterface) {
throw new \InvalidArgumentException('You must supply an instance of OAuth2\ClientAssertionType\ClientAssertionTypeInterface or only use grant types which implement OAuth2\ClientAssertionType\ClientAssertionTypeInterface');
}
}
}
$this->clientAssertionType = $clientAssertionType;
$this->accessToken = $accessToken;
$this->clientStorage = $clientStorage;
foreach ($grantTypes as $grantType) {
$this->addGrantType($grantType);
}
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response)
{
if ($token = $this->grantAccessToken($request, $response)) {
// @see http://tools.ietf.org/html/rfc6749#section-5.1
// server MUST disable caching in headers when tokens are involved
$response->setStatusCode(200);
$response->addParameters($token);
$response->addHttpHeaders(array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache'));
}
}
/**
* Grant or deny a requested access token.
* This would be called from the "/token" endpoint as defined in the spec.
* You can call your endpoint whatever you want.
*
* @param $request - RequestInterface
* Request object to grant access token
*
* @throws InvalidArgumentException
* @throws LogicException
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @see http://tools.ietf.org/html/rfc6749#section-10.6
* @see http://tools.ietf.org/html/rfc6749#section-4.1.3
*
* @ingroup oauth2_section_4
*/
public function grantAccessToken(RequestInterface $request, ResponseInterface $response)
{
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
$response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2');
$response->addHttpHeaders(array('Allow' => 'POST'));
return null;
}
/**
* Determine grant type from request
* and validate the request for that grant type
*/
if (!$grantTypeIdentifier = $request->request('grant_type')) {
$response->setError(400, 'invalid_request', 'The grant type was not specified in the request');
return null;
}
if (!isset($this->grantTypes[$grantTypeIdentifier])) {
/* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */
$response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier));
return null;
}
$grantType = $this->grantTypes[$grantTypeIdentifier];
/**
* Retrieve the client information from the request
* ClientAssertionTypes allow for grant types which also assert the client data
* in which case ClientAssertion is handled in the validateRequest method
*
* @see OAuth2\GrantType\JWTBearer
* @see OAuth2\GrantType\ClientCredentials
*/
if (!$grantType instanceof ClientAssertionTypeInterface) {
if (!$this->clientAssertionType->validateRequest($request, $response)) {
return null;
}
$clientId = $this->clientAssertionType->getClientId();
}
/**
* Retrieve the grant type information from the request
* The GrantTypeInterface object handles all validation
* If the object is an instance of ClientAssertionTypeInterface,
* That logic is handled here as well
*/
if (!$grantType->validateRequest($request, $response)) {
return null;
}
if ($grantType instanceof ClientAssertionTypeInterface) {
$clientId = $grantType->getClientId();
} else {
// validate the Client ID (if applicable)
if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) {
$response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier));
return null;
}
}
/**
* Validate the client can use the requested grant type
*/
if (!$this->clientStorage->checkRestrictedGrantType($clientId, $grantTypeIdentifier)) {
$response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id');
return false;
}
/**
* Validate the scope of the token
*
* requestedScope - the scope specified in the token request
* availableScope - the scope associated with the grant type
* ex: in the case of the "Authorization Code" grant type,
* the scope is specified in the authorize request
*
* @see http://tools.ietf.org/html/rfc6749#section-3.3
*/
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
$availableScope = $grantType->getScope();
if ($requestedScope) {
// validate the requested scope
if ($availableScope) {
if (!$this->scopeUtil->checkScope($requestedScope, $availableScope)) {
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this request');
return null;
}
} else {
// validate the client has access to this scope
if ($clientScope = $this->clientStorage->getClientScope($clientId)) {
if (!$this->scopeUtil->checkScope($requestedScope, $clientScope)) {
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this client');
return false;
}
} elseif (!$this->scopeUtil->scopeExists($requestedScope)) {
$response->setError(400, 'invalid_scope', 'An unsupported scope was requested');
return null;
}
}
} elseif ($availableScope) {
// use the scope associated with this grant type
$requestedScope = $availableScope;
} else {
// use a globally-defined default scope
$defaultScope = $this->scopeUtil->getDefaultScope($clientId);
// "false" means default scopes are not allowed
if (false === $defaultScope) {
$response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter');
return null;
}
$requestedScope = $defaultScope;
}
return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope);
}
/**
* addGrantType
*
* @param grantType - OAuth2\GrantTypeInterface
* the grant type to add for the specified identifier
* @param identifier - string
* a string passed in as "grant_type" in the response that will call this grantType
*/
public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
{
if (is_null($identifier) || is_numeric($identifier)) {
$identifier = $grantType->getQuerystringIdentifier();
}
$this->grantTypes[$identifier] = $grantType;
}
public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response)
{
if ($this->revokeToken($request, $response)) {
$response->setStatusCode(200);
$response->addParameters(array('revoked' => true));
}
}
/**
* Revoke a refresh or access token. Returns true on success and when tokens are invalid
*
* Note: invalid tokens do not cause an error response since the client
* cannot handle such an error in a reasonable way. Moreover, the
* purpose of the revocation request, invalidating the particular token,
* is already achieved.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool|null
*/
public function revokeToken(RequestInterface $request, ResponseInterface $response)
{
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
$response->setError(405, 'invalid_request', 'The request method must be POST when revoking an access token', '#section-3.2');
$response->addHttpHeaders(array('Allow' => 'POST'));
return null;
}
$token_type_hint = $request->request('token_type_hint');
if (!in_array($token_type_hint, array(null, 'access_token', 'refresh_token'), true)) {
$response->setError(400, 'invalid_request', 'Token type hint must be either \'access_token\' or \'refresh_token\'');
return null;
}
$token = $request->request('token');
if ($token === null) {
$response->setError(400, 'invalid_request', 'Missing token parameter to revoke');
return null;
}
// @todo remove this check for v2.0
if (!method_exists($this->accessToken, 'revokeToken')) {
$class = get_class($this->accessToken);
throw new \RuntimeException("AccessToken {$class} does not implement required revokeToken method");
}
$this->accessToken->revokeToken($token, $token_type_hint);
return true;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a token is being requested.
* it is called to handle all grant types the application supports.
* It also validates the client's credentials
*
* ex:
* > $tokenController->handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response());
* > $response->send();
*
*/
interface TokenControllerInterface
{
/**
* handleTokenRequest
*
* @param $request
* OAuth2\RequestInterface - The current http request
* @param $response
* OAuth2\ResponseInterface - An instance of OAuth2\ResponseInterface to contain the response data
*
*/
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response);
public function grantAccessToken(RequestInterface $request, ResponseInterface $response);
}

View file

@ -0,0 +1,11 @@
<?php
namespace OAuth2\Encryption;
interface EncryptionInterface
{
public function encode($payload, $key, $algorithm = null);
public function decode($payload, $key, $algorithm = null);
public function urlSafeB64Encode($data);
public function urlSafeB64Decode($b64);
}

View file

@ -0,0 +1,47 @@
<?php
namespace OAuth2\Encryption;
/**
* Bridge file to use the firebase/php-jwt package for JWT encoding and decoding.
* @author Francis Chuang <francis.chuang@gmail.com>
*/
class FirebaseJwt implements EncryptionInterface
{
public function __construct()
{
if (!class_exists('\JWT')) {
throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"');
}
}
public function encode($payload, $key, $alg = 'HS256', $keyId = null)
{
return \JWT::encode($payload, $key, $alg, $keyId);
}
public function decode($jwt, $key = null, $allowedAlgorithms = null)
{
try {
//Maintain BC: Do not verify if no algorithms are passed in.
if (!$allowedAlgorithms) {
$key = null;
}
return (array)\JWT::decode($jwt, $key, $allowedAlgorithms);
} catch (\Exception $e) {
return false;
}
}
public function urlSafeB64Encode($data)
{
return \JWT::urlsafeB64Encode($data);
}
public function urlSafeB64Decode($b64)
{
return \JWT::urlsafeB64Decode($b64);
}
}

View file

@ -0,0 +1,173 @@
<?php
namespace OAuth2\Encryption;
/**
* @link https://github.com/F21/jwt
* @author F21
*/
class Jwt implements EncryptionInterface
{
public function encode($payload, $key, $algo = 'HS256')
{
$header = $this->generateJwtHeader($payload, $algo);
$segments = array(
$this->urlSafeB64Encode(json_encode($header)),
$this->urlSafeB64Encode(json_encode($payload))
);
$signing_input = implode('.', $segments);
$signature = $this->sign($signing_input, $key, $algo);
$segments[] = $this->urlsafeB64Encode($signature);
return implode('.', $segments);
}
public function decode($jwt, $key = null, $allowedAlgorithms = true)
{
if (!strpos($jwt, '.')) {
return false;
}
$tks = explode('.', $jwt);
if (count($tks) != 3) {
return false;
}
list($headb64, $payloadb64, $cryptob64) = $tks;
if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
return false;
}
if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
return false;
}
$sig = $this->urlSafeB64Decode($cryptob64);
if ((bool) $allowedAlgorithms) {
if (!isset($header['alg'])) {
return false;
}
// check if bool arg supplied here to maintain BC
if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
return false;
}
if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
return false;
}
}
return $payload;
}
private function verifySignature($signature, $input, $key, $algo = 'HS256')
{
// use constants when possible, for HipHop support
switch ($algo) {
case'HS256':
case'HS384':
case'HS512':
return $this->hash_equals(
$this->sign($input, $key, $algo),
$signature
);
case 'RS256':
return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
case 'RS384':
return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
case 'RS512':
return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
default:
throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
}
}
private function sign($input, $key, $algo = 'HS256')
{
switch ($algo) {
case 'HS256':
return hash_hmac('sha256', $input, $key, true);
case 'HS384':
return hash_hmac('sha384', $input, $key, true);
case 'HS512':
return hash_hmac('sha512', $input, $key, true);
case 'RS256':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
case 'RS384':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
case 'RS512':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
default:
throw new \Exception("Unsupported or invalid signing algorithm.");
}
}
private function generateRSASignature($input, $key, $algo)
{
if (!openssl_sign($input, $signature, $key, $algo)) {
throw new \Exception("Unable to sign data.");
}
return $signature;
}
public function urlSafeB64Encode($data)
{
$b64 = base64_encode($data);
$b64 = str_replace(array('+', '/', "\r", "\n", '='),
array('-', '_'),
$b64);
return $b64;
}
public function urlSafeB64Decode($b64)
{
$b64 = str_replace(array('-', '_'),
array('+', '/'),
$b64);
return base64_decode($b64);
}
/**
* Override to create a custom header
*/
protected function generateJwtHeader($payload, $algorithm)
{
return array(
'typ' => 'JWT',
'alg' => $algorithm,
);
}
protected function hash_equals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$diff = strlen($a) ^ strlen($b);
for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\AuthorizationCodeInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode implements GrantTypeInterface
{
protected $storage;
protected $authCode;
/**
* @param OAuth2\Storage\AuthorizationCodeInterface $storage REQUIRED Storage class for retrieving authorization code information
*/
public function __construct(AuthorizationCodeInterface $storage)
{
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'authorization_code';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request('code')) {
$response->setError(400, 'invalid_request', 'Missing parameter: "code" is required');
return false;
}
$code = $request->request('code');
if (!$authCode = $this->storage->getAuthorizationCode($code)) {
$response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client');
return false;
}
/*
* 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request
* @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3
*/
if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) {
$response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
return false;
}
}
if (!isset($authCode['expires'])) {
throw new \Exception('Storage must return authcode with a value for "expires"');
}
if ($authCode["expires"] < time()) {
$response->setError(400, 'invalid_grant', "The authorization code has expired");
return false;
}
if (!isset($authCode['code'])) {
$authCode['code'] = $code; // used to expire the code after the access token is granted
}
$this->authCode = $authCode;
return true;
}
public function getClientId()
{
return $this->authCode['client_id'];
}
public function getScope()
{
return isset($this->authCode['scope']) ? $this->authCode['scope'] : null;
}
public function getUserId()
{
return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$token = $accessToken->createAccessToken($client_id, $user_id, $scope);
$this->storage->expireAuthorizationCode($this->authCode['code']);
return $token;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\HttpBasic;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\Storage\ClientCredentialsInterface;
/**
* @author Brent Shaffer <bshafs at gmail dot com>
*
* @see OAuth2\ClientAssertionType_HttpBasic
*/
class ClientCredentials extends HttpBasic implements GrantTypeInterface
{
private $clientData;
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
/**
* The client credentials grant type MUST only be used by confidential clients
*
* @see http://tools.ietf.org/html/rfc6749#section-4.4
*/
$config['allow_public_clients'] = false;
parent::__construct($storage, $config);
}
public function getQuerystringIdentifier()
{
return 'client_credentials';
}
public function getScope()
{
$this->loadClientData();
return isset($this->clientData['scope']) ? $this->clientData['scope'] : null;
}
public function getUserId()
{
$this->loadClientData();
return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
/**
* Client Credentials Grant does NOT include a refresh token
*
* @see http://tools.ietf.org/html/rfc6749#section-4.4.3
*/
$includeRefreshToken = false;
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
private function loadClientData()
{
if (!$this->clientData) {
$this->clientData = $this->storage->getClientDetails($this->getClientId());
}
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Interface for all OAuth2 Grant Types
*/
interface GrantTypeInterface
{
public function getQuerystringIdentifier();
public function validateRequest(RequestInterface $request, ResponseInterface $response);
public function getClientId();
public function getUserId();
public function getScope();
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope);
}

View file

@ -0,0 +1,226 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\Storage\JwtBearerInterface;
use OAuth2\Encryption\Jwt;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft.
*
* @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4
*
* @author F21
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
{
private $jwt;
protected $storage;
protected $audience;
protected $jwtUtil;
protected $allowedAlgorithms;
/**
* Creates an instance of the JWT bearer grant type.
*
* @param OAuth2\Storage\JWTBearerInterface|JwtBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type.
* @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
* @param EncryptionInterface|OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs.
* @param array $config
*/
public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array())
{
$this->storage = $storage;
$this->audience = $audience;
if (is_null($jwtUtil)) {
$jwtUtil = new Jwt();
}
$this->config = array_merge(array(
'allowed_algorithms' => array('RS256', 'RS384', 'RS512')
), $config);
$this->jwtUtil = $jwtUtil;
$this->allowedAlgorithms = $this->config['allowed_algorithms'];
}
/**
* Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant.
*
* @return
* The string identifier for grant_type.
*
* @see OAuth2\GrantType\GrantTypeInterface::getQuerystringIdentifier()
*/
public function getQuerystringIdentifier()
{
return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
}
/**
* Validates the data from the decoded JWT.
*
* @return
* TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.
*
* @see OAuth2\GrantType\GrantTypeInterface::getTokenData()
*/
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("assertion")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
return null;
}
// Store the undecoded JWT for later use
$undecodedJWT = $request->request('assertion');
// Decode the JWT
$jwt = $this->jwtUtil->decode($request->request('assertion'), null, false);
if (!$jwt) {
$response->setError(400, 'invalid_request', "JWT is malformed");
return null;
}
// ensure these properties contain a value
// @todo: throw malformed error for missing properties
$jwt = array_merge(array(
'scope' => null,
'iss' => null,
'sub' => null,
'aud' => null,
'exp' => null,
'nbf' => null,
'iat' => null,
'jti' => null,
'typ' => null,
), $jwt);
if (!isset($jwt['iss'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
return null;
}
if (!isset($jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
return null;
}
if (!isset($jwt['exp'])) {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
return null;
}
// Check expiration
if (ctype_digit($jwt['exp'])) {
if ($jwt['exp'] <= time()) {
$response->setError(400, 'invalid_grant', "JWT has expired");
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
return null;
}
// Check the not before time
if ($notBefore = $jwt['nbf']) {
if (ctype_digit($notBefore)) {
if ($notBefore > time()) {
$response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
return null;
}
}
// Check the audience if required to match
if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
$response->setError(400, 'invalid_grant', "Invalid audience (aud)");
return null;
}
// Check the jti (nonce)
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7
if (isset($jwt['jti'])) {
$jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
//Reject if jti is used and jwt is still valid (exp parameter has not expired).
if ($jti && $jti['expires'] > time()) {
$response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used");
return null;
} else {
$this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
}
}
// Get the iss's public key
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
return null;
}
// Verify the JWT
if (!$this->jwtUtil->decode($undecodedJWT, $key, $this->allowedAlgorithms)) {
$response->setError(400, 'invalid_grant', "JWT failed signature verification");
return null;
}
$this->jwt = $jwt;
return true;
}
public function getClientId()
{
return $this->jwt['iss'];
}
public function getUserId()
{
return $this->jwt['sub'];
}
public function getScope()
{
return null;
}
/**
* Creates an access token that is NOT associated with a refresh token.
* If a subject (sub) the name of the user/account we are accessing data on behalf of.
*
* @see OAuth2\GrantType\GrantTypeInterface::createAccessToken()
*/
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$includeRefreshToken = false;
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\RefreshTokenInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class RefreshToken implements GrantTypeInterface
{
private $refreshToken;
protected $storage;
protected $config;
/**
* @param OAuth2\Storage\RefreshTokenInterface $storage REQUIRED Storage class for retrieving refresh token information
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
* 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using
* );
* </code>
*/
public function __construct(RefreshTokenInterface $storage, $config = array())
{
$this->config = array_merge(array(
'always_issue_new_refresh_token' => false,
'unset_refresh_token_after_use' => true
), $config);
// to preserve B.C. with v1.6
// @see https://github.com/bshaffer/oauth2-server-php/pull/580
// @todo - remove in v2.0
if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) {
$this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token'];
}
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'refresh_token';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("refresh_token")) {
$response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required');
return null;
}
if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) {
$response->setError(400, 'invalid_grant', 'Invalid refresh token');
return null;
}
if ($refreshToken['expires'] > 0 && $refreshToken["expires"] < time()) {
$response->setError(400, 'invalid_grant', 'Refresh token has expired');
return null;
}
// store the refresh token locally so we can delete it when a new refresh token is generated
$this->refreshToken = $refreshToken;
return true;
}
public function getClientId()
{
return $this->refreshToken['client_id'];
}
public function getUserId()
{
return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null;
}
public function getScope()
{
return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
/*
* It is optional to force a new refresh token when a refresh token is used.
* However, if a new refresh token is issued, the old one MUST be expired
* @see http://tools.ietf.org/html/rfc6749#section-6
*/
$issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
$unsetRefreshToken = $this->config['unset_refresh_token_after_use'];
$token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken);
if ($unsetRefreshToken) {
$this->storage->unsetRefreshToken($this->refreshToken['refresh_token']);
}
return $token;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\UserCredentialsInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class UserCredentials implements GrantTypeInterface
{
private $userInfo;
protected $storage;
/**
* @param OAuth2\Storage\UserCredentialsInterface $storage REQUIRED Storage class for retrieving user credentials information
*/
public function __construct(UserCredentialsInterface $storage)
{
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'password';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("password") || !$request->request("username")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required');
return null;
}
if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) {
$response->setError(401, 'invalid_grant', 'Invalid username and password combination');
return null;
}
$userInfo = $this->storage->getUserDetails($request->request("username"));
if (empty($userInfo)) {
$response->setError(400, 'invalid_grant', 'Unable to retrieve user information');
return null;
}
if (!isset($userInfo['user_id'])) {
throw new \LogicException("you must set the user_id on the array returned by getUserDetails");
}
$this->userInfo = $userInfo;
return true;
}
public function getClientId()
{
return null;
}
public function getUserId()
{
return $this->userInfo['user_id'];
}
public function getScope()
{
return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
return $accessToken->createAccessToken($client_id, $user_id, $scope);
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\Controller\AuthorizeController as BaseAuthorizeController;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\AuthorizeControllerInterface
*/
class AuthorizeController extends BaseAuthorizeController implements AuthorizeControllerInterface
{
private $nonce;
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
{
$prompt = $request->query('prompt', 'consent');
if ($prompt == 'none') {
if (is_null($user_id)) {
$error = 'login_required';
$error_message = 'The user must log in';
} else {
$error = 'interaction_required';
$error_message = 'The user must grant access to your application';
}
} else {
$error = 'consent_required';
$error_message = 'The user denied access to your application';
}
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message);
}
protected function buildAuthorizeParameters($request, $response, $user_id)
{
if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) {
return;
}
// Generate an id token if needed.
if ($this->needsIdToken($this->getScope()) && $this->getResponseType() == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
$params['id_token'] = $this->responseTypes['id_token']->createIdToken($this->getClientId(), $user_id, $this->nonce);
}
// add the nonce to return with the redirect URI
$params['nonce'] = $this->nonce;
return $params;
}
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
{
if (!parent::validateAuthorizeRequest($request, $response)) {
return false;
}
$nonce = $request->query('nonce');
// Validate required nonce for "id_token" and "id_token token"
if (!$nonce && in_array($this->getResponseType(), array(self::RESPONSE_TYPE_ID_TOKEN, self::RESPONSE_TYPE_ID_TOKEN_TOKEN))) {
$response->setError(400, 'invalid_nonce', 'This application requires you specify a nonce parameter');
return false;
}
$this->nonce = $nonce;
return true;
}
protected function getValidResponseTypes()
{
return array(
self::RESPONSE_TYPE_ACCESS_TOKEN,
self::RESPONSE_TYPE_AUTHORIZATION_CODE,
self::RESPONSE_TYPE_ID_TOKEN,
self::RESPONSE_TYPE_ID_TOKEN_TOKEN,
self::RESPONSE_TYPE_CODE_ID_TOKEN,
);
}
/**
* Returns whether the current request needs to generate an id token.
*
* ID Tokens are a part of the OpenID Connect specification, so this
* method checks whether OpenID Connect is enabled in the server settings
* and whether the openid scope was requested.
*
* @param $request_scope
* A space-separated string of scopes.
*
* @return
* TRUE if an id token is needed, FALSE otherwise.
*/
public function needsIdToken($request_scope)
{
// see if the "openid" scope exists in the requested scope
return $this->scopeUtil->checkScope('openid', $request_scope);
}
public function getNonce()
{
return $this->nonce;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace OAuth2\OpenID\Controller;
interface AuthorizeControllerInterface
{
const RESPONSE_TYPE_ID_TOKEN = 'id_token';
const RESPONSE_TYPE_ID_TOKEN_TOKEN = 'id_token token';
const RESPONSE_TYPE_CODE_ID_TOKEN = 'code id_token';
}

View file

@ -0,0 +1,58 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\Scope;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\Storage\AccessTokenInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
use OAuth2\Controller\ResourceController;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\UserInfoControllerInterface
*/
class UserInfoController extends ResourceController implements UserInfoControllerInterface
{
private $token;
protected $tokenType;
protected $tokenStorage;
protected $userClaimsStorage;
protected $config;
protected $scopeUtil;
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, UserClaimsInterface $userClaimsStorage, $config = array(), ScopeInterface $scopeUtil = null)
{
$this->tokenType = $tokenType;
$this->tokenStorage = $tokenStorage;
$this->userClaimsStorage = $userClaimsStorage;
$this->config = array_merge(array(
'www_realm' => 'Service',
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$this->verifyResourceRequest($request, $response, 'openid')) {
return;
}
$token = $this->getToken();
$claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']);
// The sub Claim MUST always be returned in the UserInfo Response.
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
$claims += array(
'sub' => $token['user_id'],
);
$response->addParameters($claims);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when the user claims for OpenID Connect's
* UserInfo endpoint should be returned.
*
* ex:
* > $response = new OAuth2\Response();
* > $userInfoController->handleUserInfoRequest(
* > OAuth2\Request::createFromGlobals(),
* > $response;
* > $response->send();
*
*/
interface UserInfoControllerInterface
{
public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response);
}

View file

@ -0,0 +1,33 @@
<?php
namespace OAuth2\OpenID\GrantType;
use OAuth2\GrantType\AuthorizationCode as BaseAuthorizationCode;
use OAuth2\ResponseType\AccessTokenInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode extends BaseAuthorizationCode
{
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$includeRefreshToken = true;
if (isset($this->authCode['id_token'])) {
// OpenID Connect requests include the refresh token only if the
// offline_access scope has been requested and granted.
$scopes = explode(' ', trim($scope));
$includeRefreshToken = in_array('offline_access', $scopes);
}
$token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
if (isset($this->authCode['id_token'])) {
$token['id_token'] = $this->authCode['id_token'];
}
$this->storage->expireAuthorizationCode($this->authCode['code']);
return $token;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AuthorizationCode as BaseAuthorizationCode;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as AuthorizationCodeStorageInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface
{
public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
{
parent::__construct($storage, $config);
}
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null, 'id_token' => null);
$result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token']);
if (isset($params['state'])) {
$result['query']['state'] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
/**
* Handle the creation of the authorization code.
*
* @param $client_id
* Client identifier related to the authorization code
* @param $user_id
* User ID associated with the authorization code
* @param $redirect_uri
* An absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope
* (optional) Scopes to be stored in space-separated string.
* @param $id_token
* (optional) The OpenID Connect id_token.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null)
{
$code = $this->generateAuthorizationCode();
$this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token);
return $code;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
{
/**
* Handle the creation of the authorization code.
*
* @param $client_id Client identifier related to the authorization code
* @param $user_id User ID associated with the authorization code
* @param $redirect_uri An absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope OPTIONAL Scopes to be stored in space-separated string.
* @param $id_token OPTIONAL The OpenID Connect id_token.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null);
}

View file

@ -0,0 +1,24 @@
<?php
namespace OAuth2\OpenID\ResponseType;
class CodeIdToken implements CodeIdTokenInterface
{
protected $authCode;
protected $idToken;
public function __construct(AuthorizationCodeInterface $authCode, IdTokenInterface $idToken)
{
$this->authCode = $authCode;
$this->idToken = $idToken;
}
public function getAuthorizeResponse($params, $user_id = null)
{
$result = $this->authCode->getAuthorizeResponse($params, $user_id);
$id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce']);
$result[1]['query']['id_token'] = $id_token;
return $result;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface CodeIdTokenInterface extends ResponseTypeInterface
{
}

View file

@ -0,0 +1,124 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\Encryption\Jwt;
use OAuth2\Storage\PublicKeyInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
class IdToken implements IdTokenInterface
{
protected $userClaimsStorage;
protected $publicKeyStorage;
protected $config;
protected $encryptionUtil;
public function __construct(UserClaimsInterface $userClaimsStorage, PublicKeyInterface $publicKeyStorage, array $config = array(), EncryptionInterface $encryptionUtil = null)
{
$this->userClaimsStorage = $userClaimsStorage;
$this->publicKeyStorage = $publicKeyStorage;
if (is_null($encryptionUtil)) {
$encryptionUtil = new Jwt();
}
$this->encryptionUtil = $encryptionUtil;
if (!isset($config['issuer'])) {
throw new \LogicException('config parameter "issuer" must be set');
}
$this->config = array_merge(array(
'id_lifetime' => 3600,
), $config);
}
public function getAuthorizeResponse($params, $userInfo = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null, 'nonce' => null);
// create the id token.
list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
$userClaims = $this->userClaimsStorage->getUserClaims($user_id, $params['scope']);
$id_token = $this->createIdToken($params['client_id'], $userInfo, $params['nonce'], $userClaims, null);
$result["fragment"] = array('id_token' => $id_token);
if (isset($params['state'])) {
$result["fragment"]["state"] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null)
{
// pull auth_time from user info if supplied
list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
$token = array(
'iss' => $this->config['issuer'],
'sub' => $user_id,
'aud' => $client_id,
'iat' => time(),
'exp' => time() + $this->config['id_lifetime'],
'auth_time' => $auth_time,
);
if ($nonce) {
$token['nonce'] = $nonce;
}
if ($userClaims) {
$token += $userClaims;
}
if ($access_token) {
$token['at_hash'] = $this->createAtHash($access_token, $client_id);
}
return $this->encodeToken($token, $client_id);
}
protected function createAtHash($access_token, $client_id = null)
{
// maps HS256 and RS256 to sha256, etc.
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
$hash_algorithm = 'sha' . substr($algorithm, 2);
$hash = hash($hash_algorithm, $access_token);
$at_hash = substr($hash, 0, strlen($hash) / 2);
return $this->encryptionUtil->urlSafeB64Encode($at_hash);
}
protected function encodeToken(array $token, $client_id = null)
{
$private_key = $this->publicKeyStorage->getPrivateKey($client_id);
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
return $this->encryptionUtil->encode($token, $private_key, $algorithm);
}
private function getUserIdAndAuthTime($userInfo)
{
$auth_time = null;
// support an array for user_id / auth_time
if (is_array($userInfo)) {
if (!isset($userInfo['user_id'])) {
throw new \LogicException('if $user_id argument is an array, user_id index must be set');
}
$auth_time = isset($userInfo['auth_time']) ? $userInfo['auth_time'] : null;
$user_id = $userInfo['user_id'];
} else {
$user_id = $userInfo;
}
if (is_null($auth_time)) {
$auth_time = time();
}
// userInfo is a scalar, and so this is the $user_id. Auth Time is null
return array($user_id, $auth_time);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface IdTokenInterface extends ResponseTypeInterface
{
/**
* Create the id token.
*
* If Authorization Code Flow is used, the id_token is generated when the
* authorization code is issued, and later returned from the token endpoint
* together with the access_token.
* If the Implicit Flow is used, the token and id_token are generated and
* returned together.
*
* @param string $client_id The client id.
* @param string $user_id The user id.
* @param string $nonce OPTIONAL The nonce.
* @param string $userClaims OPTIONAL Claims about the user.
* @param string $access_token OPTIONAL The access token, if known.
*
* @return string The ID Token represented as a JSON Web Token (JWT).
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken
*/
public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null);
}

View file

@ -0,0 +1,27 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AccessTokenInterface;
class IdTokenToken implements IdTokenTokenInterface
{
protected $accessToken;
protected $idToken;
public function __construct(AccessTokenInterface $accessToken, IdTokenInterface $idToken)
{
$this->accessToken = $accessToken;
$this->idToken = $idToken;
}
public function getAuthorizeResponse($params, $user_id = null)
{
$result = $this->accessToken->getAuthorizeResponse($params, $user_id);
$access_token = $result[1]['fragment']['access_token'];
$id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token);
$result[1]['fragment']['id_token'] = $id_token;
return $result;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface IdTokenTokenInterface extends ResponseTypeInterface
{
}

View file

@ -0,0 +1,37 @@
<?php
namespace OAuth2\OpenID\Storage;
use OAuth2\Storage\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
/**
* Implement this interface to specify where the OAuth2 Server
* should get/save authorization codes for the "Authorization Code"
* grant type
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
{
/**
* Take the provided authorization code values and store them somewhere.
*
* This function should be the storage counterpart to getAuthCode().
*
* If storage fails for some reason, we're not currently checking for
* any sort of success/failure, so you should bail out of the script
* and provide a descriptive fail message.
*
* Required for OAuth2::GRANT_TYPE_AUTH_CODE.
*
* @param $code authorization code to be stored.
* @param $client_id client identifier to be stored.
* @param $user_id user identifier to be stored.
* @param string $redirect_uri redirect URI(s) to be stored in a space-separated string.
* @param int $expires expiration to be stored as a Unix timestamp.
* @param string $scope OPTIONAL scopes to be stored in space-separated string.
* @param string $id_token OPTIONAL the OpenID Connect id_token.
*
* @ingroup oauth2_section_4
*/
public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null);
}

View file

@ -0,0 +1,38 @@
<?php
namespace OAuth2\OpenID\Storage;
/**
* Implement this interface to specify where the OAuth2 Server
* should retrieve user claims for the OpenID Connect id_token.
*/
interface UserClaimsInterface
{
// valid scope values to pass into the user claims API call
const VALID_CLAIMS = 'profile email address phone';
// fields returned for the claims above
const PROFILE_CLAIM_VALUES = 'name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at';
const EMAIL_CLAIM_VALUES = 'email email_verified';
const ADDRESS_CLAIM_VALUES = 'formatted street_address locality region postal_code country';
const PHONE_CLAIM_VALUES = 'phone_number phone_number_verified';
/**
* Return claims about the provided user id.
*
* Groups of claims are returned based on the requested scopes. No group
* is required, and no claim is required.
*
* @param $user_id
* The id of the user for which claims should be returned.
* @param $scope
* The requested scope.
* Scopes with matching claims: profile, email, address, phone.
*
* @return
* An array in the claim => value format.
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
*/
public function getUserClaims($user_id, $scope);
}

View file

@ -0,0 +1,213 @@
<?php
namespace OAuth2;
/**
* OAuth2\Request
* This class is taken from the Symfony2 Framework and is part of the Symfony package.
* See Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony)
*/
class Request implements RequestInterface
{
public $attributes;
public $request;
public $query;
public $server;
public $files;
public $cookies;
public $headers;
public $content;
/**
* Constructor.
*
* @param array $query The GET parameters
* @param array $request The POST parameters
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array $cookies The COOKIE parameters
* @param array $files The FILES parameters
* @param array $server The SERVER parameters
* @param string $content The raw body data
*
* @api
*/
public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null)
{
$this->initialize($query, $request, $attributes, $cookies, $files, $server, $content, $headers);
}
/**
* Sets the parameters for this request.
*
* This method also re-initializes all properties.
*
* @param array $query The GET parameters
* @param array $request The POST parameters
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array $cookies The COOKIE parameters
* @param array $files The FILES parameters
* @param array $server The SERVER parameters
* @param string $content The raw body data
*
* @api
*/
public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null)
{
$this->request = $request;
$this->query = $query;
$this->attributes = $attributes;
$this->cookies = $cookies;
$this->files = $files;
$this->server = $server;
$this->content = $content;
$this->headers = is_null($headers) ? $this->getHeadersFromServer($this->server) : $headers;
}
public function query($name, $default = null)
{
return isset($this->query[$name]) ? $this->query[$name] : $default;
}
public function request($name, $default = null)
{
return isset($this->request[$name]) ? $this->request[$name] : $default;
}
public function server($name, $default = null)
{
return isset($this->server[$name]) ? $this->server[$name] : $default;
}
public function headers($name, $default = null)
{
$headers = array_change_key_case($this->headers);
$name = strtolower($name);
return isset($headers[$name]) ? $headers[$name] : $default;
}
public function getAllQueryParameters()
{
return $this->query;
}
/**
* Returns the request body content.
*
* @param Boolean $asResource If true, a resource will be returned
*
* @return string|resource The request body content or a resource to read the body stream.
*/
public function getContent($asResource = false)
{
if (false === $this->content || (true === $asResource && null !== $this->content)) {
throw new \LogicException('getContent() can only be called once when using the resource return type.');
}
if (true === $asResource) {
$this->content = false;
return fopen('php://input', 'rb');
}
if (null === $this->content) {
$this->content = file_get_contents('php://input');
}
return $this->content;
}
private function getHeadersFromServer($server)
{
$headers = array();
foreach ($server as $key => $value) {
if (0 === strpos($key, 'HTTP_')) {
$headers[substr($key, 5)] = $value;
}
// CONTENT_* are not prefixed with HTTP_
elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) {
$headers[$key] = $value;
}
}
if (isset($server['PHP_AUTH_USER'])) {
$headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER'];
$headers['PHP_AUTH_PW'] = isset($server['PHP_AUTH_PW']) ? $server['PHP_AUTH_PW'] : '';
} else {
/*
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
* For this workaround to work, add this line to your .htaccess file:
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
*
* A sample .htaccess file:
* RewriteEngine On
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
* RewriteCond %{REQUEST_FILENAME} !-f
* RewriteRule ^(.*)$ app.php [QSA,L]
*/
$authorizationHeader = null;
if (isset($server['HTTP_AUTHORIZATION'])) {
$authorizationHeader = $server['HTTP_AUTHORIZATION'];
} elseif (isset($server['REDIRECT_HTTP_AUTHORIZATION'])) {
$authorizationHeader = $server['REDIRECT_HTTP_AUTHORIZATION'];
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = (array) apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
if (isset($requestHeaders['Authorization'])) {
$authorizationHeader = trim($requestHeaders['Authorization']);
}
}
if (null !== $authorizationHeader) {
$headers['AUTHORIZATION'] = $authorizationHeader;
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic
if (0 === stripos($authorizationHeader, 'basic')) {
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
if (count($exploded) == 2) {
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
}
}
}
}
// PHP_AUTH_USER/PHP_AUTH_PW
if (isset($headers['PHP_AUTH_USER'])) {
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']);
}
return $headers;
}
/**
* Creates a new request with values from PHP's super globals.
*
* @return Request A new request
*
* @api
*/
public static function createFromGlobals()
{
$class = get_called_class();
$request = new $class($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
$contentType = $request->server('CONTENT_TYPE', '');
$requestMethod = $request->server('REQUEST_METHOD', 'GET');
if (0 === strpos($contentType, 'application/x-www-form-urlencoded')
&& in_array(strtoupper($requestMethod), array('PUT', 'DELETE'))
) {
parse_str($request->getContent(), $data);
$request->request = $data;
} elseif (0 === strpos($contentType, 'application/json')
&& in_array(strtoupper($requestMethod), array('POST', 'PUT', 'DELETE'))
) {
$data = json_decode($request->getContent(), true);
$request->request = $data;
}
return $request;
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace OAuth2;
interface RequestInterface
{
public function query($name, $default = null);
public function request($name, $default = null);
public function server($name, $default = null);
public function headers($name, $default = null);
public function getAllQueryParameters();
}

View file

@ -0,0 +1,369 @@
<?php
namespace OAuth2;
/**
* Class to handle OAuth2 Responses in a graceful way. Use this interface
* to output the proper OAuth2 responses.
*
* @see OAuth2\ResponseInterface
*
* This class borrows heavily from the Symfony2 Framework and is part of the symfony package
* @see Symfony\Component\HttpFoundation\Request (https://github.com/symfony/symfony)
*/
class Response implements ResponseInterface
{
public $version;
protected $statusCode = 200;
protected $statusText;
protected $parameters = array();
protected $httpHeaders = array();
public static $statusTexts = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
);
public function __construct($parameters = array(), $statusCode = 200, $headers = array())
{
$this->setParameters($parameters);
$this->setStatusCode($statusCode);
$this->setHttpHeaders($headers);
$this->version = '1.1';
}
/**
* Converts the response object to string containing all headers and the response content.
*
* @return string The response with headers and content
*/
public function __toString()
{
$headers = array();
foreach ($this->httpHeaders as $name => $value) {
$headers[$name] = (array) $value;
}
return
sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
$this->getHttpHeadersAsString($headers)."\r\n".
$this->getResponseBody();
}
/**
* Returns the build header line.
*
* @param string $name The header name
* @param string $value The header value
*
* @return string The built header line
*/
protected function buildHeader($name, $value)
{
return sprintf("%s: %s\n", $name, $value);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($statusCode, $text = null)
{
$this->statusCode = (int) $statusCode;
if ($this->isInvalid()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $statusCode));
}
$this->statusText = false === $text ? '' : (null === $text ? self::$statusTexts[$this->statusCode] : $text);
}
public function getStatusText()
{
return $this->statusText;
}
public function getParameters()
{
return $this->parameters;
}
public function setParameters(array $parameters)
{
$this->parameters = $parameters;
}
public function addParameters(array $parameters)
{
$this->parameters = array_merge($this->parameters, $parameters);
}
public function getParameter($name, $default = null)
{
return isset($this->parameters[$name]) ? $this->parameters[$name] : $default;
}
public function setParameter($name, $value)
{
$this->parameters[$name] = $value;
}
public function setHttpHeaders(array $httpHeaders)
{
$this->httpHeaders = $httpHeaders;
}
public function setHttpHeader($name, $value)
{
$this->httpHeaders[$name] = $value;
}
public function addHttpHeaders(array $httpHeaders)
{
$this->httpHeaders = array_merge($this->httpHeaders, $httpHeaders);
}
public function getHttpHeaders()
{
return $this->httpHeaders;
}
public function getHttpHeader($name, $default = null)
{
return isset($this->httpHeaders[$name]) ? $this->httpHeaders[$name] : $default;
}
public function getResponseBody($format = 'json')
{
switch ($format) {
case 'json':
return json_encode($this->parameters);
case 'xml':
// this only works for single-level arrays
$xml = new \SimpleXMLElement('<response/>');
foreach ($this->parameters as $key => $param) {
$xml->addChild($key, $param);
}
return $xml->asXML();
}
throw new \InvalidArgumentException(sprintf('The format %s is not supported', $format));
}
public function send($format = 'json')
{
// headers have already been sent by the developer
if (headers_sent()) {
return;
}
switch ($format) {
case 'json':
$this->setHttpHeader('Content-Type', 'application/json');
break;
case 'xml':
$this->setHttpHeader('Content-Type', 'text/xml');
break;
}
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText));
foreach ($this->getHttpHeaders() as $name => $header) {
header(sprintf('%s: %s', $name, $header));
}
echo $this->getResponseBody($format);
}
public function setError($statusCode, $error, $errorDescription = null, $errorUri = null)
{
$parameters = array(
'error' => $error,
'error_description' => $errorDescription,
);
if (!is_null($errorUri)) {
if (strlen($errorUri) > 0 && $errorUri[0] == '#') {
// we are referencing an oauth bookmark (for brevity)
$errorUri = 'http://tools.ietf.org/html/rfc6749' . $errorUri;
}
$parameters['error_uri'] = $errorUri;
}
$httpHeaders = array(
'Cache-Control' => 'no-store'
);
$this->setStatusCode($statusCode);
$this->addParameters($parameters);
$this->addHttpHeaders($httpHeaders);
if (!$this->isClientError() && !$this->isServerError()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not an error ("%s" given).', $statusCode));
}
}
public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null)
{
if (empty($url)) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
}
$parameters = array();
if (!is_null($state)) {
$parameters['state'] = $state;
}
if (!is_null($error)) {
$this->setError(400, $error, $errorDescription, $errorUri);
}
$this->setStatusCode($statusCode);
$this->addParameters($parameters);
if (count($this->parameters) > 0) {
// add parameters to URL redirection
$parts = parse_url($url);
$sep = isset($parts['query']) && count($parts['query']) > 0 ? '&' : '?';
$url .= $sep . http_build_query($this->parameters);
}
$this->addHttpHeaders(array('Location' => $url));
if (!$this->isRedirection()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $statusCode));
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
/**
* @return Boolean
*
* @api
*/
public function isInvalid()
{
return $this->statusCode < 100 || $this->statusCode >= 600;
}
/**
* @return Boolean
*
* @api
*/
public function isInformational()
{
return $this->statusCode >= 100 && $this->statusCode < 200;
}
/**
* @return Boolean
*
* @api
*/
public function isSuccessful()
{
return $this->statusCode >= 200 && $this->statusCode < 300;
}
/**
* @return Boolean
*
* @api
*/
public function isRedirection()
{
return $this->statusCode >= 300 && $this->statusCode < 400;
}
/**
* @return Boolean
*
* @api
*/
public function isClientError()
{
return $this->statusCode >= 400 && $this->statusCode < 500;
}
/**
* @return Boolean
*
* @api
*/
public function isServerError()
{
return $this->statusCode >= 500 && $this->statusCode < 600;
}
/*
* Functions from Symfony2 HttpFoundation - output pretty header
*/
private function getHttpHeadersAsString($headers)
{
if (count($headers) == 0) {
return '';
}
$max = max(array_map('strlen', array_keys($headers))) + 1;
$content = '';
ksort($headers);
foreach ($headers as $name => $values) {
foreach ($values as $value) {
$content .= sprintf("%-{$max}s %s\r\n", $this->beautifyHeaderName($name).':', $value);
}
}
return $content;
}
private function beautifyHeaderName($name)
{
return preg_replace_callback('/\-(.)/', array($this, 'beautifyCallback'), ucfirst($name));
}
private function beautifyCallback($match)
{
return '-'.strtoupper($match[1]);
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace OAuth2;
/**
* Interface which represents an object response. Meant to handle and display the proper OAuth2 Responses
* for errors and successes
*
* @see OAuth2\Response
*/
interface ResponseInterface
{
public function addParameters(array $parameters);
public function addHttpHeaders(array $httpHeaders);
public function setStatusCode($statusCode);
public function setError($statusCode, $name, $description = null, $uri = null);
public function setRedirect($statusCode, $url, $state = null, $error = null, $errorDescription = null, $errorUri = null);
public function getParameter($name);
}

View file

@ -0,0 +1,194 @@
<?php
namespace OAuth2\ResponseType;
use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface;
use OAuth2\Storage\RefreshTokenInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AccessToken implements AccessTokenInterface
{
protected $tokenStorage;
protected $refreshStorage;
protected $config;
/**
* @param OAuth2\Storage\AccessTokenInterface $tokenStorage REQUIRED Storage class for saving access token information
* @param OAuth2\Storage\RefreshTokenInterface $refreshStorage OPTIONAL Storage class for saving refresh token information
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'token_type' => 'bearer', // token type identifier
* 'access_lifetime' => 3600, // time before access token expires
* 'refresh_token_lifetime' => 1209600, // time before refresh token expires
* );
* </endcode>
*/
public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array())
{
$this->tokenStorage = $tokenStorage;
$this->refreshStorage = $refreshStorage;
$this->config = array_merge(array(
'token_type' => 'bearer',
'access_lifetime' => 3600,
'refresh_token_lifetime' => 1209600,
), $config);
}
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null);
/*
* a refresh token MUST NOT be included in the fragment
*
* @see http://tools.ietf.org/html/rfc6749#section-4.2.2
*/
$includeRefreshToken = false;
$result["fragment"] = $this->createAccessToken($params['client_id'], $user_id, $params['scope'], $includeRefreshToken);
if (isset($params['state'])) {
$result["fragment"]["state"] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
/**
* Handle the creation of access token, also issue refresh token if supported / desirable.
*
* @param $client_id client identifier related to the access token.
* @param $user_id user ID associated with the access token
* @param $scope OPTIONAL scopes to be stored in space-separated string.
* @param bool $includeRefreshToken if true, a new refresh_token will be added to the response
*
* @see http://tools.ietf.org/html/rfc6749#section-5
* @ingroup oauth2_section_5
*/
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
$token = array(
"access_token" => $this->generateAccessToken(),
"expires_in" => $this->config['access_lifetime'],
"token_type" => $this->config['token_type'],
"scope" => $scope
);
$this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
/*
* Issue a refresh token also, if we support them
*
* Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
* is supplied in the constructor
*/
if ($includeRefreshToken && $this->refreshStorage) {
$token["refresh_token"] = $this->generateRefreshToken();
$expires = 0;
if ($this->config['refresh_token_lifetime'] > 0) {
$expires = time() + $this->config['refresh_token_lifetime'];
}
$this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, $expires, $scope);
}
return $token;
}
/**
* Generates an unique access token.
*
* Implementing classes may want to override this function to implement
* other access token generation schemes.
*
* @return
* An unique access token.
*
* @ingroup oauth2_section_4
*/
protected function generateAccessToken()
{
if (function_exists('mcrypt_create_iv')) {
$randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM);
if ($randomData !== false && strlen($randomData) === 20) {
return bin2hex($randomData);
}
}
if (function_exists('openssl_random_pseudo_bytes')) {
$randomData = openssl_random_pseudo_bytes(20);
if ($randomData !== false && strlen($randomData) === 20) {
return bin2hex($randomData);
}
}
if (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 20);
if ($randomData !== false && strlen($randomData) === 20) {
return bin2hex($randomData);
}
}
// Last resort which you probably should just get rid of:
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
return substr(hash('sha512', $randomData), 0, 40);
}
/**
* Generates an unique refresh token
*
* Implementing classes may want to override this function to implement
* other refresh token generation schemes.
*
* @return
* An unique refresh.
*
* @ingroup oauth2_section_4
* @see OAuth2::generateAccessToken()
*/
protected function generateRefreshToken()
{
return $this->generateAccessToken(); // let's reuse the same scheme for token generation
}
/**
* Handle the revoking of refresh tokens, and access tokens if supported / desirable
* RFC7009 specifies that "If the server is unable to locate the token using
* the given hint, it MUST extend its search across all of its supported token types"
*
* @param $token
* @param null $tokenTypeHint
* @return boolean
*/
public function revokeToken($token, $tokenTypeHint = null)
{
if ($tokenTypeHint == 'refresh_token') {
if ($this->refreshStorage && $revoked = $this->refreshStorage->unsetRefreshToken($token)) {
return true;
}
}
/** @TODO remove in v2 */
if (!method_exists($this->tokenStorage, 'unsetAccessToken')) {
throw new \RuntimeException(
sprintf('Token storage %s must implement unsetAccessToken method', get_class($this->tokenStorage)
));
}
$revoked = $this->tokenStorage->unsetAccessToken($token);
// if a typehint is supplied and fails, try other storages
// @see https://tools.ietf.org/html/rfc7009#section-2.1
if (!$revoked && $tokenTypeHint != 'refresh_token') {
if ($this->refreshStorage) {
$revoked = $this->refreshStorage->unsetRefreshToken($token);
}
}
return $revoked;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace OAuth2\ResponseType;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AccessTokenInterface extends ResponseTypeInterface
{
/**
* Handle the creation of access token, also issue refresh token if supported / desirable.
*
* @param $client_id client identifier related to the access token.
* @param $user_id user ID associated with the access token
* @param $scope OPTONAL scopes to be stored in space-separated string.
* @param bool $includeRefreshToken if true, a new refresh_token will be added to the response
*
* @see http://tools.ietf.org/html/rfc6749#section-5
* @ingroup oauth2_section_5
*/
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true);
/**
* Handle the revoking of refresh tokens, and access tokens if supported / desirable
*
* @param $token
* @param $tokenTypeHint
* @return mixed
*
* @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x
*/
//public function revokeToken($token, $tokenTypeHint);
}

View file

@ -0,0 +1,100 @@
<?php
namespace OAuth2\ResponseType;
use OAuth2\Storage\AuthorizationCodeInterface as AuthorizationCodeStorageInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode implements AuthorizationCodeInterface
{
protected $storage;
protected $config;
public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
{
$this->storage = $storage;
$this->config = array_merge(array(
'enforce_redirect' => false,
'auth_code_lifetime' => 30,
), $config);
}
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null);
$result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']);
if (isset($params['state'])) {
$result['query']['state'] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
/**
* Handle the creation of the authorization code.
*
* @param $client_id
* Client identifier related to the authorization code
* @param $user_id
* User ID associated with the authorization code
* @param $redirect_uri
* An absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope
* (optional) Scopes to be stored in space-separated string.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null)
{
$code = $this->generateAuthorizationCode();
$this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope);
return $code;
}
/**
* @return
* TRUE if the grant type requires a redirect_uri, FALSE if not
*/
public function enforceRedirect()
{
return $this->config['enforce_redirect'];
}
/**
* Generates an unique auth code.
*
* Implementing classes may want to override this function to implement
* other auth code generation schemes.
*
* @return
* An unique auth code.
*
* @ingroup oauth2_section_4
*/
protected function generateAuthorizationCode()
{
$tokenLen = 40;
if (function_exists('mcrypt_create_iv')) {
$randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$randomData = openssl_random_pseudo_bytes(100);
} elseif (@file_exists('/dev/urandom')) { // Get 100 bytes of random data
$randomData = file_get_contents('/dev/urandom', false, null, 0, 100) . uniqid(mt_rand(), true);
} else {
$randomData = mt_rand() . mt_rand() . mt_rand() . mt_rand() . microtime(true) . uniqid(mt_rand(), true);
}
return substr(hash('sha512', $randomData), 0, $tokenLen);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace OAuth2\ResponseType;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AuthorizationCodeInterface extends ResponseTypeInterface
{
/**
* @return
* TRUE if the grant type requires a redirect_uri, FALSE if not
*/
public function enforceRedirect();
/**
* Handle the creation of the authorization code.
*
* @param $client_id client identifier related to the authorization code
* @param $user_id user id associated with the authorization code
* @param $redirect_uri an absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope OPTIONAL scopes to be stored in space-separated string.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null);
}

View file

@ -0,0 +1,124 @@
<?php
namespace OAuth2\ResponseType;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\Encryption\Jwt;
use OAuth2\Storage\AccessTokenInterface as AccessTokenStorageInterface;
use OAuth2\Storage\RefreshTokenInterface;
use OAuth2\Storage\PublicKeyInterface;
use OAuth2\Storage\Memory;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class JwtAccessToken extends AccessToken
{
protected $publicKeyStorage;
protected $encryptionUtil;
/**
* @param $config
* - store_encrypted_token_string (bool true)
* whether the entire encrypted string is stored,
* or just the token ID is stored
*/
public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null)
{
$this->publicKeyStorage = $publicKeyStorage;
$config = array_merge(array(
'store_encrypted_token_string' => true,
'issuer' => ''
), $config);
if (is_null($tokenStorage)) {
// a pass-thru, so we can call the parent constructor
$tokenStorage = new Memory();
}
if (is_null($encryptionUtil)) {
$encryptionUtil = new Jwt();
}
$this->encryptionUtil = $encryptionUtil;
parent::__construct($tokenStorage, $refreshStorage, $config);
}
/**
* Handle the creation of access token, also issue refresh token if supported / desirable.
*
* @param $client_id
* Client identifier related to the access token.
* @param $user_id
* User ID associated with the access token
* @param $scope
* (optional) Scopes to be stored in space-separated string.
* @param bool $includeRefreshToken
* If true, a new refresh_token will be added to the response
*
* @see http://tools.ietf.org/html/rfc6749#section-5
* @ingroup oauth2_section_5
*/
public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
// token to encrypt
$expires = time() + $this->config['access_lifetime'];
$id = $this->generateAccessToken();
$jwtAccessToken = array(
'id' => $id, // for BC (see #591)
'jti' => $id,
'iss' => $this->config['issuer'],
'aud' => $client_id,
'sub' => $user_id,
'exp' => $expires,
'iat' => time(),
'token_type' => $this->config['token_type'],
'scope' => $scope
);
/*
* Encode the token data into a single access_token string
*/
$access_token = $this->encodeToken($jwtAccessToken, $client_id);
/*
* Save the token to a secondary storage. This is implemented on the
* OAuth2\Storage\JwtAccessToken side, and will not actually store anything,
* if no secondary storage has been supplied
*/
$token_to_store = $this->config['store_encrypted_token_string'] ? $access_token : $jwtAccessToken['id'];
$this->tokenStorage->setAccessToken($token_to_store, $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);
// token to return to the client
$token = array(
'access_token' => $access_token,
'expires_in' => $this->config['access_lifetime'],
'token_type' => $this->config['token_type'],
'scope' => $scope
);
/*
* Issue a refresh token also, if we support them
*
* Refresh Tokens are considered supported if an instance of OAuth2\Storage\RefreshTokenInterface
* is supplied in the constructor
*/
if ($includeRefreshToken && $this->refreshStorage) {
$refresh_token = $this->generateRefreshToken();
$expires = 0;
if ($this->config['refresh_token_lifetime'] > 0) {
$expires = time() + $this->config['refresh_token_lifetime'];
}
$this->refreshStorage->setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope);
$token['refresh_token'] = $refresh_token;
}
return $token;
}
protected function encodeToken(array $token, $client_id = null)
{
$private_key = $this->publicKeyStorage->getPrivateKey($client_id);
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
return $this->encryptionUtil->encode($token, $private_key, $algorithm);
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace OAuth2\ResponseType;
interface ResponseTypeInterface
{
public function getAuthorizeResponse($params, $user_id = null);
}

View file

@ -0,0 +1,103 @@
<?php
namespace OAuth2;
use OAuth2\Storage\Memory;
use OAuth2\Storage\ScopeInterface as ScopeStorageInterface;
/**
* @see OAuth2\ScopeInterface
*/
class Scope implements ScopeInterface
{
protected $storage;
/**
* @param mixed @storage
* Either an array of supported scopes, or an instance of OAuth2\Storage\ScopeInterface
*/
public function __construct($storage = null)
{
if (is_null($storage) || is_array($storage)) {
$storage = new Memory((array) $storage);
}
if (!$storage instanceof ScopeStorageInterface) {
throw new \InvalidArgumentException("Argument 1 to OAuth2\Scope must be null, an array, or instance of OAuth2\Storage\ScopeInterface");
}
$this->storage = $storage;
}
/**
* Check if everything in required scope is contained in available scope.
*
* @param $required_scope
* A space-separated string of scopes.
*
* @return
* TRUE if everything in required scope is contained in available scope,
* and FALSE if it isn't.
*
* @see http://tools.ietf.org/html/rfc6749#section-7
*
* @ingroup oauth2_section_7
*/
public function checkScope($required_scope, $available_scope)
{
$required_scope = explode(' ', trim($required_scope));
$available_scope = explode(' ', trim($available_scope));
return (count(array_diff($required_scope, $available_scope)) == 0);
}
/**
* Check if the provided scope exists in storage.
*
* @param $scope
* A space-separated string of scopes.
*
* @return
* TRUE if it exists, FALSE otherwise.
*/
public function scopeExists($scope)
{
// Check reserved scopes first.
$scope = explode(' ', trim($scope));
$reservedScope = $this->getReservedScopes();
$nonReservedScopes = array_diff($scope, $reservedScope);
if (count($nonReservedScopes) == 0) {
return true;
} else {
// Check the storage for non-reserved scopes.
$nonReservedScopes = implode(' ', $nonReservedScopes);
return $this->storage->scopeExists($nonReservedScopes);
}
}
public function getScopeFromRequest(RequestInterface $request)
{
// "scope" is valid if passed in either POST or QUERY
return $request->request('scope', $request->query('scope'));
}
public function getDefaultScope($client_id = null)
{
return $this->storage->getDefaultScope($client_id);
}
/**
* Get reserved scopes needed by the server.
*
* In case OpenID Connect is used, these scopes must include:
* 'openid', offline_access'.
*
* @return
* An array of reserved scopes.
*/
public function getReservedScopes()
{
return array('openid', 'offline_access');
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace OAuth2;
use OAuth2\Storage\ScopeInterface as ScopeStorageInterface;
/**
* Class to handle scope implementation logic
*
* @see OAuth2\Storage\ScopeInterface
*/
interface ScopeInterface extends ScopeStorageInterface
{
/**
* Check if everything in required scope is contained in available scope.
*
* @param $required_scope
* A space-separated string of scopes.
*
* @return
* TRUE if everything in required scope is contained in available scope,
* and FALSE if it isn't.
*
* @see http://tools.ietf.org/html/rfc6749#section-7
*
* @ingroup oauth2_section_7
*/
public function checkScope($required_scope, $available_scope);
/**
* Return scope info from request
*
* @param OAuth2\RequestInterface
* Request object to check
*
* @return
* string representation of requested scope
*/
public function getScopeFromRequest(RequestInterface $request);
}

View file

@ -0,0 +1,832 @@
<?php
namespace OAuth2;
use OAuth2\Controller\ResourceControllerInterface;
use OAuth2\Controller\ResourceController;
use OAuth2\OpenID\Controller\UserInfoControllerInterface;
use OAuth2\OpenID\Controller\UserInfoController;
use OAuth2\OpenID\Controller\AuthorizeController as OpenIDAuthorizeController;
use OAuth2\OpenID\ResponseType\AuthorizationCode as OpenIDAuthorizationCodeResponseType;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
use OAuth2\OpenID\GrantType\AuthorizationCode as OpenIDAuthorizationCodeGrantType;
use OAuth2\Controller\AuthorizeControllerInterface;
use OAuth2\Controller\AuthorizeController;
use OAuth2\Controller\TokenControllerInterface;
use OAuth2\Controller\TokenController;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\ClientAssertionType\HttpBasic;
use OAuth2\ResponseType\ResponseTypeInterface;
use OAuth2\ResponseType\AuthorizationCode as AuthorizationCodeResponseType;
use OAuth2\ResponseType\AccessToken;
use OAuth2\ResponseType\JwtAccessToken;
use OAuth2\OpenID\ResponseType\CodeIdToken;
use OAuth2\OpenID\ResponseType\IdToken;
use OAuth2\OpenID\ResponseType\IdTokenToken;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\TokenType\Bearer;
use OAuth2\GrantType\GrantTypeInterface;
use OAuth2\GrantType\UserCredentials;
use OAuth2\GrantType\ClientCredentials;
use OAuth2\GrantType\RefreshToken;
use OAuth2\GrantType\AuthorizationCode;
use OAuth2\Storage\JwtAccessToken as JwtAccessTokenStorage;
use OAuth2\Storage\JwtAccessTokenInterface;
/**
* Server class for OAuth2
* This class serves as a convience class which wraps the other Controller classes
*
* @see OAuth2\Controller\ResourceController
* @see OAuth2\Controller\AuthorizeController
* @see OAuth2\Controller\TokenController
*/
class Server implements ResourceControllerInterface,
AuthorizeControllerInterface,
TokenControllerInterface,
UserInfoControllerInterface
{
// misc properties
protected $response;
protected $config;
protected $storages;
// servers
protected $authorizeController;
protected $tokenController;
protected $resourceController;
protected $userInfoController;
// config classes
protected $grantTypes;
protected $responseTypes;
protected $tokenType;
protected $scopeUtil;
protected $clientAssertionType;
protected $storageMap = array(
'access_token' => 'OAuth2\Storage\AccessTokenInterface',
'authorization_code' => 'OAuth2\Storage\AuthorizationCodeInterface',
'client_credentials' => 'OAuth2\Storage\ClientCredentialsInterface',
'client' => 'OAuth2\Storage\ClientInterface',
'refresh_token' => 'OAuth2\Storage\RefreshTokenInterface',
'user_credentials' => 'OAuth2\Storage\UserCredentialsInterface',
'user_claims' => 'OAuth2\OpenID\Storage\UserClaimsInterface',
'public_key' => 'OAuth2\Storage\PublicKeyInterface',
'jwt_bearer' => 'OAuth2\Storage\JWTBearerInterface',
'scope' => 'OAuth2\Storage\ScopeInterface',
);
protected $responseTypeMap = array(
'token' => 'OAuth2\ResponseType\AccessTokenInterface',
'code' => 'OAuth2\ResponseType\AuthorizationCodeInterface',
'id_token' => 'OAuth2\OpenID\ResponseType\IdTokenInterface',
'id_token token' => 'OAuth2\OpenID\ResponseType\IdTokenTokenInterface',
'code id_token' => 'OAuth2\OpenID\ResponseType\CodeIdTokenInterface',
);
/**
* @param mixed $storage (array or OAuth2\Storage) - single object or array of objects implementing the
* required storage types (ClientCredentialsInterface and AccessTokenInterface as a minimum)
* @param array $config specify a different token lifetime, token header name, etc
* @param array $grantTypes An array of OAuth2\GrantType\GrantTypeInterface to use for granting access tokens
* @param array $responseTypes Response types to use. array keys should be "code" and and "token" for
* Access Token and Authorization Code response types
* @param OAuth2\TokenType\TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac"
* @param OAuth2\ScopeInterface $scopeUtil The scope utility class to use to validate scope
* @param OAuth2\ClientAssertionType\ClientAssertionTypeInterface $clientAssertionType The method in which to verify the client identity. Default is HttpBasic
*
* @ingroup oauth2_section_7
*/
public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null)
{
$storage = is_array($storage) ? $storage : array($storage);
$this->storages = array();
foreach ($storage as $key => $service) {
$this->addStorage($service, $key);
}
// merge all config values. These get passed to our controller objects
$this->config = array_merge(array(
'use_jwt_access_tokens' => false,
'store_encrypted_token_string' => true,
'use_openid_connect' => false,
'id_lifetime' => 3600,
'access_lifetime' => 3600,
'www_realm' => 'Service',
'token_param_name' => 'access_token',
'token_bearer_header_name' => 'Bearer',
'enforce_state' => true,
'require_exact_redirect_uri' => true,
'allow_implicit' => false,
'allow_credentials_in_request_body' => true,
'allow_public_clients' => true,
'always_issue_new_refresh_token' => false,
'unset_refresh_token_after_use' => true,
), $config);
foreach ($grantTypes as $key => $grantType) {
$this->addGrantType($grantType, $key);
}
foreach ($responseTypes as $key => $responseType) {
$this->addResponseType($responseType, $key);
}
$this->tokenType = $tokenType;
$this->scopeUtil = $scopeUtil;
$this->clientAssertionType = $clientAssertionType;
if ($this->config['use_openid_connect']) {
$this->validateOpenIdConnect();
}
}
public function getAuthorizeController()
{
if (is_null($this->authorizeController)) {
$this->authorizeController = $this->createDefaultAuthorizeController();
}
return $this->authorizeController;
}
public function getTokenController()
{
if (is_null($this->tokenController)) {
$this->tokenController = $this->createDefaultTokenController();
}
return $this->tokenController;
}
public function getResourceController()
{
if (is_null($this->resourceController)) {
$this->resourceController = $this->createDefaultResourceController();
}
return $this->resourceController;
}
public function getUserInfoController()
{
if (is_null($this->userInfoController)) {
$this->userInfoController = $this->createDefaultUserInfoController();
}
return $this->userInfoController;
}
/**
* every getter deserves a setter
*/
public function setAuthorizeController(AuthorizeControllerInterface $authorizeController)
{
$this->authorizeController = $authorizeController;
}
/**
* every getter deserves a setter
*/
public function setTokenController(TokenControllerInterface $tokenController)
{
$this->tokenController = $tokenController;
}
/**
* every getter deserves a setter
*/
public function setResourceController(ResourceControllerInterface $resourceController)
{
$this->resourceController = $resourceController;
}
/**
* every getter deserves a setter
*/
public function setUserInfoController(UserInfoControllerInterface $userInfoController)
{
$this->userInfoController = $userInfoController;
}
/**
* Return claims about the authenticated end-user.
* This would be called from the "/UserInfo" endpoint as defined in the spec.
*
* @param $request - OAuth2\RequestInterface
* Request object to grant access token
*
* @param $response - OAuth2\ResponseInterface
* Response object containing error messages (failure) or user claims (success)
*
* @throws InvalidArgumentException
* @throws LogicException
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
*/
public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$this->getUserInfoController()->handleUserInfoRequest($request, $this->response);
return $this->response;
}
/**
* Grant or deny a requested access token.
* This would be called from the "/token" endpoint as defined in the spec.
* Obviously, you can call your endpoint whatever you want.
*
* @param $request - OAuth2\RequestInterface
* Request object to grant access token
*
* @param $response - OAuth2\ResponseInterface
* Response object containing error messages (failure) or access token (success)
*
* @throws InvalidArgumentException
* @throws LogicException
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @see http://tools.ietf.org/html/rfc6749#section-10.6
* @see http://tools.ietf.org/html/rfc6749#section-4.1.3
*
* @ingroup oauth2_section_4
*/
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$this->getTokenController()->handleTokenRequest($request, $this->response);
return $this->response;
}
public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getTokenController()->grantAccessToken($request, $this->response);
return $value;
}
/**
* Handle a revoke token request
* This would be called from the "/revoke" endpoint as defined in the draft Token Revocation spec
*
* @see https://tools.ietf.org/html/rfc7009#section-2
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return Response|ResponseInterface
*/
public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$this->getTokenController()->handleRevokeRequest($request, $this->response);
return $this->response;
}
/**
* Redirect the user appropriately after approval.
*
* After the user has approved or denied the resource request the
* authorization server should call this function to redirect the user
* appropriately.
*
* @param $request
* The request should have the follow parameters set in the querystring:
* - response_type: The requested response: an access token, an
* authorization code, or both.
* - client_id: The client identifier as described in Section 2.
* - redirect_uri: An absolute URI to which the authorization server
* will redirect the user-agent to when the end-user authorization
* step is completed.
* - scope: (optional) The scope of the resource request expressed as a
* list of space-delimited strings.
* - state: (optional) An opaque value used by the client to maintain
* state between the request and callback.
* @param $is_authorized
* TRUE or FALSE depending on whether the user authorized the access.
* @param $user_id
* Identifier of user who authorized the client
*
* @see http://tools.ietf.org/html/rfc6749#section-4
*
* @ingroup oauth2_section_4
*/
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
{
$this->response = $response;
$this->getAuthorizeController()->handleAuthorizeRequest($request, $this->response, $is_authorized, $user_id);
return $this->response;
}
/**
* Pull the authorization request data out of the HTTP request.
* - The redirect_uri is OPTIONAL as per draft 20. But your implementation can enforce it
* by setting $config['enforce_redirect'] to true.
* - The state is OPTIONAL but recommended to enforce CSRF. Draft 21 states, however, that
* CSRF protection is MANDATORY. You can enforce this by setting the $config['enforce_state'] to true.
*
* The draft specifies that the parameters should be retrieved from GET, override the Response
* object to change this
*
* @return
* The authorization parameters so the authorization server can prompt
* the user for approval if valid.
*
* @see http://tools.ietf.org/html/rfc6749#section-4.1.1
* @see http://tools.ietf.org/html/rfc6749#section-10.12
*
* @ingroup oauth2_section_3
*/
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response);
return $value;
}
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null)
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope);
return $value;
}
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null)
{
$this->response = is_null($response) ? new Response() : $response;
$value = $this->getResourceController()->getAccessTokenData($request, $this->response);
return $value;
}
public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
{
if (!is_string($identifier)) {
$identifier = $grantType->getQuerystringIdentifier();
}
$this->grantTypes[$identifier] = $grantType;
// persist added grant type down to TokenController
if (!is_null($this->tokenController)) {
$this->getTokenController()->addGrantType($grantType, $identifier);
}
}
/**
* Set a storage object for the server
*
* @param $storage
* An object implementing one of the Storage interfaces
* @param $key
* If null, the storage is set to the key of each storage interface it implements
*
* @see storageMap
*/
public function addStorage($storage, $key = null)
{
// if explicitly set to a valid key, do not "magically" set below
if (isset($this->storageMap[$key])) {
if (!is_null($storage) && !$storage instanceof $this->storageMap[$key]) {
throw new \InvalidArgumentException(sprintf('storage of type "%s" must implement interface "%s"', $key, $this->storageMap[$key]));
}
$this->storages[$key] = $storage;
// special logic to handle "client" and "client_credentials" strangeness
if ($key === 'client' && !isset($this->storages['client_credentials'])) {
if ($storage instanceof \OAuth2\Storage\ClientCredentialsInterface) {
$this->storages['client_credentials'] = $storage;
}
} elseif ($key === 'client_credentials' && !isset($this->storages['client'])) {
if ($storage instanceof \OAuth2\Storage\ClientInterface) {
$this->storages['client'] = $storage;
}
}
} elseif (!is_null($key) && !is_numeric($key)) {
throw new \InvalidArgumentException(sprintf('unknown storage key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->storageMap))));
} else {
$set = false;
foreach ($this->storageMap as $type => $interface) {
if ($storage instanceof $interface) {
$this->storages[$type] = $storage;
$set = true;
}
}
if (!$set) {
throw new \InvalidArgumentException(sprintf('storage of class "%s" must implement one of [%s]', get_class($storage), implode(', ', $this->storageMap)));
}
}
}
public function addResponseType(ResponseTypeInterface $responseType, $key = null)
{
$key = $this->normalizeResponseType($key);
if (isset($this->responseTypeMap[$key])) {
if (!$responseType instanceof $this->responseTypeMap[$key]) {
throw new \InvalidArgumentException(sprintf('responseType of type "%s" must implement interface "%s"', $key, $this->responseTypeMap[$key]));
}
$this->responseTypes[$key] = $responseType;
} elseif (!is_null($key) && !is_numeric($key)) {
throw new \InvalidArgumentException(sprintf('unknown responseType key "%s", must be one of [%s]', $key, implode(', ', array_keys($this->responseTypeMap))));
} else {
$set = false;
foreach ($this->responseTypeMap as $type => $interface) {
if ($responseType instanceof $interface) {
$this->responseTypes[$type] = $responseType;
$set = true;
}
}
if (!$set) {
throw new \InvalidArgumentException(sprintf('Unknown response type %s. Please implement one of [%s]', get_class($responseType), implode(', ', $this->responseTypeMap)));
}
}
}
public function getScopeUtil()
{
if (!$this->scopeUtil) {
$storage = isset($this->storages['scope']) ? $this->storages['scope'] : null;
$this->scopeUtil = new Scope($storage);
}
return $this->scopeUtil;
}
/**
* every getter deserves a setter
*/
public function setScopeUtil($scopeUtil)
{
$this->scopeUtil = $scopeUtil;
}
protected function createDefaultAuthorizeController()
{
if (!isset($this->storages['client'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the authorize server");
}
if (0 == count($this->responseTypes)) {
$this->responseTypes = $this->getDefaultResponseTypes();
}
if ($this->config['use_openid_connect'] && !isset($this->responseTypes['id_token'])) {
$this->responseTypes['id_token'] = $this->createDefaultIdTokenResponseType();
if ($this->config['allow_implicit']) {
$this->responseTypes['id_token token'] = $this->createDefaultIdTokenTokenResponseType();
}
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri')));
if ($this->config['use_openid_connect']) {
return new OpenIDAuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
}
return new AuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil());
}
protected function createDefaultTokenController()
{
if (0 == count($this->grantTypes)) {
$this->grantTypes = $this->getDefaultGrantTypes();
}
if (is_null($this->clientAssertionType)) {
// see if HttpBasic assertion type is requred. If so, then create it from storage classes.
foreach ($this->grantTypes as $grantType) {
if (!$grantType instanceof ClientAssertionTypeInterface) {
if (!isset($this->storages['client_credentials'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientCredentialsInterface to use the token server");
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_credentials_in_request_body allow_public_clients')));
$this->clientAssertionType = new HttpBasic($this->storages['client_credentials'], $config);
break;
}
}
}
if (!isset($this->storages['client'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\ClientInterface to use the token server");
}
$accessTokenResponseType = $this->getAccessTokenResponseType();
return new TokenController($accessTokenResponseType, $this->storages['client'], $this->grantTypes, $this->clientAssertionType, $this->getScopeUtil());
}
protected function createDefaultResourceController()
{
if ($this->config['use_jwt_access_tokens']) {
// overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
$this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
}
} elseif (!isset($this->storages['access_token'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the resource server");
}
if (!$this->tokenType) {
$this->tokenType = $this->getDefaultTokenType();
}
$config = array_intersect_key($this->config, array('www_realm' => ''));
return new ResourceController($this->tokenType, $this->storages['access_token'], $config, $this->getScopeUtil());
}
protected function createDefaultUserInfoController()
{
if ($this->config['use_jwt_access_tokens']) {
// overwrites access token storage with crypto token storage if "use_jwt_access_tokens" is set
if (!isset($this->storages['access_token']) || !$this->storages['access_token'] instanceof JwtAccessTokenInterface) {
$this->storages['access_token'] = $this->createDefaultJwtAccessTokenStorage();
}
} elseif (!isset($this->storages['access_token'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\AccessTokenInterface or use JwtAccessTokens to use the UserInfo server");
}
if (!isset($this->storages['user_claims'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use the UserInfo server");
}
if (!$this->tokenType) {
$this->tokenType = $this->getDefaultTokenType();
}
$config = array_intersect_key($this->config, array('www_realm' => ''));
return new UserInfoController($this->tokenType, $this->storages['access_token'], $this->storages['user_claims'], $config, $this->getScopeUtil());
}
protected function getDefaultTokenType()
{
$config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name')));
return new Bearer($config);
}
protected function getDefaultResponseTypes()
{
$responseTypes = array();
if ($this->config['allow_implicit']) {
$responseTypes['token'] = $this->getAccessTokenResponseType();
}
if ($this->config['use_openid_connect']) {
$responseTypes['id_token'] = $this->getIdTokenResponseType();
if ($this->config['allow_implicit']) {
$responseTypes['id_token token'] = $this->getIdTokenTokenResponseType();
}
}
if (isset($this->storages['authorization_code'])) {
$config = array_intersect_key($this->config, array_flip(explode(' ', 'enforce_redirect auth_code_lifetime')));
if ($this->config['use_openid_connect']) {
if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
throw new \LogicException("Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when 'use_openid_connect' is true");
}
$responseTypes['code'] = new OpenIDAuthorizationCodeResponseType($this->storages['authorization_code'], $config);
$responseTypes['code id_token'] = new CodeIdToken($responseTypes['code'], $responseTypes['id_token']);
} else {
$responseTypes['code'] = new AuthorizationCodeResponseType($this->storages['authorization_code'], $config);
}
}
if (count($responseTypes) == 0) {
throw new \LogicException("You must supply an array of response_types in the constructor or implement a OAuth2\Storage\AuthorizationCodeInterface storage object or set 'allow_implicit' to true and implement a OAuth2\Storage\AccessTokenInterface storage object");
}
return $responseTypes;
}
protected function getDefaultGrantTypes()
{
$grantTypes = array();
if (isset($this->storages['user_credentials'])) {
$grantTypes['password'] = new UserCredentials($this->storages['user_credentials']);
}
if (isset($this->storages['client_credentials'])) {
$config = array_intersect_key($this->config, array('allow_credentials_in_request_body' => ''));
$grantTypes['client_credentials'] = new ClientCredentials($this->storages['client_credentials'], $config);
}
if (isset($this->storages['refresh_token'])) {
$config = array_intersect_key($this->config, array_flip(explode(' ', 'always_issue_new_refresh_token unset_refresh_token_after_use')));
$grantTypes['refresh_token'] = new RefreshToken($this->storages['refresh_token'], $config);
}
if (isset($this->storages['authorization_code'])) {
if ($this->config['use_openid_connect']) {
if (!$this->storages['authorization_code'] instanceof OpenIDAuthorizationCodeInterface) {
throw new \LogicException("Your authorization_code storage must implement OAuth2\OpenID\Storage\AuthorizationCodeInterface to work when 'use_openid_connect' is true");
}
$grantTypes['authorization_code'] = new OpenIDAuthorizationCodeGrantType($this->storages['authorization_code']);
} else {
$grantTypes['authorization_code'] = new AuthorizationCode($this->storages['authorization_code']);
}
}
if (count($grantTypes) == 0) {
throw new \LogicException("Unable to build default grant types - You must supply an array of grant_types in the constructor");
}
return $grantTypes;
}
protected function getAccessTokenResponseType()
{
if (isset($this->responseTypes['token'])) {
return $this->responseTypes['token'];
}
if ($this->config['use_jwt_access_tokens']) {
return $this->createDefaultJwtAccessTokenResponseType();
}
return $this->createDefaultAccessTokenResponseType();
}
protected function getIdTokenResponseType()
{
if (isset($this->responseTypes['id_token'])) {
return $this->responseTypes['id_token'];
}
return $this->createDefaultIdTokenResponseType();
}
protected function getIdTokenTokenResponseType()
{
if (isset($this->responseTypes['id_token token'])) {
return $this->responseTypes['id_token token'];
}
return $this->createDefaultIdTokenTokenResponseType();
}
/**
* For Resource Controller
*/
protected function createDefaultJwtAccessTokenStorage()
{
if (!isset($this->storages['public_key'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens");
}
$tokenStorage = null;
if (!empty($this->config['store_encrypted_token_string']) && isset($this->storages['access_token'])) {
$tokenStorage = $this->storages['access_token'];
}
// wrap the access token storage as required.
return new JwtAccessTokenStorage($this->storages['public_key'], $tokenStorage);
}
/**
* For Authorize and Token Controllers
*/
protected function createDefaultJwtAccessTokenResponseType()
{
if (!isset($this->storages['public_key'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use crypto tokens");
}
$tokenStorage = null;
if (isset($this->storages['access_token'])) {
$tokenStorage = $this->storages['access_token'];
}
$refreshStorage = null;
if (isset($this->storages['refresh_token'])) {
$refreshStorage = $this->storages['refresh_token'];
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'store_encrypted_token_string issuer access_lifetime refresh_token_lifetime')));
return new JwtAccessToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config);
}
protected function createDefaultAccessTokenResponseType()
{
if (!isset($this->storages['access_token'])) {
throw new \LogicException("You must supply a response type implementing OAuth2\ResponseType\AccessTokenInterface, or a storage object implementing OAuth2\Storage\AccessTokenInterface to use the token server");
}
$refreshStorage = null;
if (isset($this->storages['refresh_token'])) {
$refreshStorage = $this->storages['refresh_token'];
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'access_lifetime refresh_token_lifetime')));
$config['token_type'] = $this->tokenType ? $this->tokenType->getTokenType() : $this->getDefaultTokenType()->getTokenType();
return new AccessToken($this->storages['access_token'], $refreshStorage, $config);
}
protected function createDefaultIdTokenResponseType()
{
if (!isset($this->storages['user_claims'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\OpenID\Storage\UserClaimsInterface to use openid connect");
}
if (!isset($this->storages['public_key'])) {
throw new \LogicException("You must supply a storage object implementing OAuth2\Storage\PublicKeyInterface to use openid connect");
}
$config = array_intersect_key($this->config, array_flip(explode(' ', 'issuer id_lifetime')));
return new IdToken($this->storages['user_claims'], $this->storages['public_key'], $config);
}
protected function createDefaultIdTokenTokenResponseType()
{
return new IdTokenToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType());
}
protected function validateOpenIdConnect()
{
$authCodeGrant = $this->getGrantType('authorization_code');
if (!empty($authCodeGrant) && !$authCodeGrant instanceof OpenIDAuthorizationCodeGrantType) {
throw new \InvalidArgumentException('You have enabled OpenID Connect, but supplied a grant type that does not support it.');
}
}
protected function normalizeResponseType($name)
{
// for multiple-valued response types - make them alphabetical
if (!empty($name) && false !== strpos($name, ' ')) {
$types = explode(' ', $name);
sort($types);
$name = implode(' ', $types);
}
return $name;
}
public function getResponse()
{
return $this->response;
}
public function getStorages()
{
return $this->storages;
}
public function getStorage($name)
{
return isset($this->storages[$name]) ? $this->storages[$name] : null;
}
public function getGrantTypes()
{
return $this->grantTypes;
}
public function getGrantType($name)
{
return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null;
}
public function getResponseTypes()
{
return $this->responseTypes;
}
public function getResponseType($name)
{
// for multiple-valued response types - make them alphabetical
$name = $this->normalizeResponseType($name);
return isset($this->responseTypes[$name]) ? $this->responseTypes[$name] : null;
}
public function getTokenType()
{
return $this->tokenType;
}
public function getClientAssertionType()
{
return $this->clientAssertionType;
}
public function setConfig($name, $value)
{
$this->config[$name] = $value;
}
public function getConfig($name, $default = null)
{
return isset($this->config[$name]) ? $this->config[$name] : $default;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace OAuth2\Storage;
/**
* Implement this interface to specify where the OAuth2 Server
* should get/save access tokens
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AccessTokenInterface
{
/**
* Look up the supplied oauth_token from storage.
*
* We need to retrieve access token data as we create and verify tokens.
*
* @param $oauth_token
* oauth_token to be check with.
*
* @return
* An associative array as below, and return NULL if the supplied oauth_token
* is invalid:
* - expires: Stored expiration in unix timestamp.
* - client_id: (optional) Stored client identifier.
* - user_id: (optional) Stored user identifier.
* - scope: (optional) Stored scope values in space-separated string.
* - id_token: (optional) Stored id_token (if "use_openid_connect" is true).
*
* @ingroup oauth2_section_7
*/
public function getAccessToken($oauth_token);
/**
* Store the supplied access token values to storage.
*
* We need to store access token data as we create and verify tokens.
*
* @param $oauth_token oauth_token to be stored.
* @param $client_id client identifier to be stored.
* @param $user_id user identifier to be stored.
* @param int $expires expiration to be stored as a Unix timestamp.
* @param string $scope OPTIONAL Scopes to be stored in space-separated string.
*
* @ingroup oauth2_section_4
*/
public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null);
/**
* Expire an access token.
*
* This is not explicitly required in the spec, but if defined in a draft RFC for token
* revoking (RFC 7009) https://tools.ietf.org/html/rfc7009
*
* @param $access_token
* Access token to be expired.
*
* @ingroup oauth2_section_6
*
* @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x
*/
//public function unsetAccessToken($access_token);
}

Some files were not shown because too many files have changed in this diff Show more