From bbe396d3c2a2ab7dcc28851a1c8a5e5ee7cfb6b7 Mon Sep 17 00:00:00 2001 From: Max Uetrecht Date: Sun, 22 Sep 2019 17:38:03 +0200 Subject: [PATCH 1/7] [Postfix] Add NO_RENEGOTIATION to tls_ssl_options --- data/conf/postfix/main.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index f7238631..98e81a34 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -149,7 +149,7 @@ smtpd_tls_protocols = !SSLv2, !SSLv3 smtpd_tls_security_level = may tls_preempt_cipherlist = yes -tls_ssl_options = NO_COMPRESSION +tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_resource_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, From 8c26371d6dbb0eea6e01b6de97c768092985e6df Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 25 Sep 2019 12:53:14 +0200 Subject: [PATCH 2/7] [SQL upgrade] Catch more errors --- data/Dockerfiles/dockerapi/server.py | 2 +- data/Dockerfiles/phpfpm/docker-entrypoint.sh | 3 ++- docker-compose.yml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py index 027c2f00..ede9433c 100644 --- a/data/Dockerfiles/dockerapi/server.py +++ b/data/Dockerfiles/dockerapi/server.py @@ -228,7 +228,7 @@ class container_post(Resource): container.restart() return jsonify(type='warning', msg='mysql_upgrade: upgrade was applied', text=sql_return.output.decode('utf-8')) else: - return jsonify(type='error', msg='mysql_upgrade: error running command') + return jsonify(type='error', msg='mysql_upgrade: error running command', text=sql_return.output.decode('utf-8')) # api call: container_post - post_action: exec - cmd: reload - task: dovecot diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 11088ad8..5a27119c 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -57,7 +57,8 @@ until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do echo "MySQL is up-to-date - debug output:" echo ${SQL_FULL_UPGRADE_RETURN} else - echo "No valid reponse for mysql_upgrade was received" + echo "No valid reponse for mysql_upgrade was received, debug output:" + echo ${SQL_FULL_UPGRADE_RETURN} fi done diff --git a/docker-compose.yml b/docker-compose.yml index 46944f56..9d3a4d91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,7 +95,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.46 + image: mailcow/phpfpm:1.47 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: @@ -394,7 +394,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:1.33 + image: mailcow/dockerapi:1.34 restart: always build: ./data/Dockerfiles/dockerapi oom_kill_disable: true From c3e313796eadcb5871f68330da0756c4bd982b21 Mon Sep 17 00:00:00 2001 From: Geitenijs <40541903+Geitenijs@users.noreply.github.com> Date: Fri, 27 Sep 2019 23:33:41 +0200 Subject: [PATCH 3/7] Update lang.nl.php --- data/web/lang/lang.nl.php | 56 +++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php index a373589e..33210f6b 100644 --- a/data/web/lang/lang.nl.php +++ b/data/web/lang/lang.nl.php @@ -15,14 +15,14 @@ $lang['footer']['restarting_container'] = 'Container wordt herstart, even geduld $lang['footer']['restart_container_info'] = 'Belangrijk: Een herstart kan enige tijd in beslag nemen, wacht aub totdat dit proces voltooid is.
Deze pagina zal zichzelf verversen zodra het proces voltooid is.'; $lang['footer']['confirm_delete'] = 'Bevestig verwijdering'; -$lang['footer']['delete_these_items'] = 'Bevestig de wijzigingen aan het volgende object:'; +$lang['footer']['delete_these_items'] = 'Bevestig de wijzigingen aan het volgende object'; $lang['footer']['delete_now'] = 'Nu verwijderen'; $lang['footer']['cancel'] = 'Annuleren'; $lang['footer']['hibp_nok'] = 'Dit is een potentieel onveilig wachtwoord!'; $lang['footer']['hibp_ok'] = 'Dit wachtwoord is relatief veilig'; -$lang['danger']['transport_dest_exists'] = "Transportbestemming bestaat reeds"; +$lang['danger']['transport_dest_exists'] = 'Transportbestemming "%s" bestaat reeds'; $lang['danger']['unlimited_quota_acl'] = "Onbeperkt quotum geweigerd door toegangscontrole"; $lang['danger']['mysql_error'] = "MySQL-fout: %s"; $lang['danger']['redis_error'] = "Redis-fout: %s"; @@ -34,7 +34,7 @@ $lang['success']['verified_u2f_login'] = "U2F succesvol geverifieerd"; $lang['success']['verified_yotp_login'] = "Yubico OTP succesvol geverifieerd"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP-verificatie mislukt: %s"; $lang['danger']['ip_list_empty'] = "Lijst met toegestane IP-adressen dient ingevuld te worden"; -$lang['danger']['invalid_destination'] = "Formaat van bestemming is ongeldig"; +$lang['danger']['invalid_destination'] = 'Formaat van bestemming "%s" is ongeldig'; $lang['danger']['invalid_nexthop'] = "Formaat van nexthop is ongeldig"; $lang['danger']['invalid_nexthop_authenticated'] = "Er bestaat al een nexthop met andere inloggegevens. Pas deze gegevens voor de reeds bestaande nexthop eerst aan."; $lang['danger']['next_hop_interferes'] = "%s interfereert met nexthop %s"; @@ -45,6 +45,7 @@ $lang['success']['queue_command_success'] = "Opdracht succesvol voltooid"; $lang['danger']['unknown'] = "Er is een onbekende fout opgetreden"; $lang['danger']['malformed_username'] = "Ongeldige gebruikersnaam"; $lang['info']['awaiting_tfa_confirmation'] = "In afwachting van tweefactorauthenticatie..."; +$lang['info']['session_expires'] = "Je huidige sessie verloopt over ongeveer 15 seconden"; $lang['success']['logged_in_as'] = "Succesvol ingelogd als %s"; $lang['danger']['login_failed'] = "Aanmelding mislukt"; $lang['danger']['set_acl_failed'] = "Toegangscontrole kon niet worden ingesteld"; @@ -114,6 +115,7 @@ $lang['success']['resource_modified'] = "Wijzigingen aan postvak %s zijn opgesla $lang['success']['object_modified'] = "Wijzigingen aan object %s zijn opgeslagen"; $lang['success']['f2b_modified'] = "Wijzigingen aan Fail2ban zijn opgeslagen"; $lang['danger']['targetd_not_found'] = "Doeldomein %s niet gevonden"; +$lang['danger']['targetd_relay_domain'] = "Doeldomein %s is een doorgeschakeld domein"; $lang['success']['aliasd_added'] = "Aliasdomein %s is toegevoegd"; $lang['success']['aliasd_modified'] = "Wijzigingen aan aliasadres %s zijn opgeslagen"; $lang['success']['domain_modified'] = "Wijzigingen aan domein %s zijn opgeslagen"; @@ -170,7 +172,7 @@ $lang['user']['user_change_fn'] = ""; $lang['user']['user_settings'] = 'Gebruikersinstellingen'; $lang['user']['mailbox_details'] = 'Postvakdetails'; $lang['user']['change_password'] = 'Verander wachtwoord'; -$lang['user']['client_configuration'] = "Laat configuratiegidsen voor e-mailprogramma's zien"; +$lang['user']['client_configuration'] = "Toon configuratiegidsen voor e-mailprogramma's"; $lang['user']['new_password'] = 'Nieuw wachtwoord'; $lang['user']['save_changes'] = 'Wijzigingen opslaan'; $lang['user']['password_now'] = 'Huidig wachtwoord'; @@ -179,9 +181,9 @@ $lang['user']['new_password_description'] = 'Vereisten: 6 tekens lang, letters e $lang['user']['spam_aliases'] = 'Tijdelijk e-mailadres'; $lang['user']['alias'] = 'Alias'; $lang['user']['shared_aliases'] = 'Gedeelde aliasadressen'; -$lang['user']['shared_aliases_desc'] = 'Een gedeeld aliasadres wordt niet beïnvloed door gebruiker-specifieke instellingen. Een aangepast spamfilter kan eventueel worden ingesteld door een beheerder.'; +$lang['user']['shared_aliases_desc'] = 'Een gedeeld aliasadres wordt niet beïnvloedt door gebruiker-specifieke instellingen. Een aangepast spamfilter kan eventueel worden ingesteld door een beheerder.'; $lang['user']['direct_aliases'] = 'Directe aliasadressen'; -$lang['user']['direct_aliases_desc'] = 'Directe aliasadressen worden beïnvloed door spamfilters en het versleutelingsbeleid.'; +$lang['user']['direct_aliases_desc'] = 'Directe aliasadressen worden beïnvloedt door spamfilters en het versleutelingsbeleid.'; $lang['user']['is_catch_all'] = 'Catch-all voor domeinen'; $lang['user']['aliases_also_send_as'] = 'Toegestaan om te verzenden als'; $lang['user']['aliases_send_as_all'] = 'Controleer verzendtoegang voor de volgende domeinen, inclusief aliassen, niet'; @@ -214,7 +216,7 @@ $lang['user']['spamfilter_table_add'] = 'Voeg toe'; $lang['user']['spamfilter_green'] = 'Groen: dit bericht is geen spam.'; $lang['user']['spamfilter_yellow'] = 'Geel: dit bericht is mogelijk spam en zal in de spamfolder geplaatst worden.'; $lang['user']['spamfilter_red'] = 'Rood: dit bericht is spam en zal, op basis van de instellingen, worden geweigerd of in de quarantaine worden geplaatst.'; -$lang['user']['spamfilter_default_score'] = 'Standaardwaarden:'; +$lang['user']['spamfilter_default_score'] = 'Standaardwaarden'; $lang['user']['spamfilter_hint'] = 'De eerste waarde omschrijft een lage spamscore, de tweede een hoge spamscore.'; $lang['user']['spamfilter_table_domain_policy'] = "n.v.t. (domeinbeleid)"; $lang['user']['waiting'] = "Wachten"; @@ -274,8 +276,8 @@ $lang['mailbox']['tls_map_dest_info'] = 'Voorbeeld: example.org, .example.org, m $lang['mailbox']['tls_map_policy'] = 'Beleid'; $lang['mailbox']['tls_map_parameters'] = 'Parameters'; $lang['mailbox']['tls_map_parameters_info'] = 'Voorbeeld: protocols=!SSLv2 ciphers=medium exclude=3DES'; -$lang['mailbox']['booking_0'] = 'Laat altijd zien als vrij'; -$lang['mailbox']['booking_lt0'] = 'Onbeperkt, maar laat zien als bezet wanneer geboekt'; +$lang['mailbox']['booking_0'] = 'Toon altijd als vrij'; +$lang['mailbox']['booking_lt0'] = 'Onbeperkt, maar toon als bezet wanneer geboekt'; $lang['mailbox']['booking_custom'] = 'Zet vast op een specifiek aantal boekingen'; $lang['mailbox']['booking_0_short'] = 'Altijd vrij'; $lang['mailbox']['booking_lt0_short'] = 'Softlimiet'; @@ -329,6 +331,12 @@ $lang['mailbox']['last_run_reset'] = 'Plan volgende'; $lang['mailbox']['sieve_info'] = 'Het is mogelijk om meerdere filters per gebruiker in te stellen, maar er kan slechts één voorfilter en één nafilter tegelijkertijd actief zijn.
Elk filter zal in de aangegeven volgorde worden verwerkt. Noch een mislukt script, noch een gespecificeerde "keep;" stopt met het verwerken van volgende scripts.
Globaal voorfilter → Voorfilter → Gebruikersscripts → Nafilter → Globaal nafilter'; $lang['info']['no_action'] = 'Geen handeling van toepassing'; + +$lang['edit']['sogo_visible'] = 'Alias is zichtbaar in SOGo'; +$lang['edit']['sogo_visible_info'] = 'Deze optie beïnvloedt enkel objecten die kunnen worden weergegeven in SOGo (gedeelde of niet-gedeelde aliasadressen die naar minstens één postvak verwijzen).'; +$lang['mailbox']['sogo_visible'] = 'Alias is zichtbaar in SOGo'; +$lang['mailbox']['sogo_visible_y'] = 'Toon alias in SOGo'; +$lang['mailbox']['sogo_visible_n'] = 'Verberg alias in SOGo'; $lang['edit']['syncjob'] = 'Wijzig synchronisatietaak'; $lang['edit']['client_id'] = 'Client ID'; $lang['edit']['client_secret'] = 'Client secret'; @@ -415,8 +423,15 @@ $lang['acl']['filters'] = 'Filters'; $lang['acl']['ratelimit'] = 'Ratelimit'; $lang['acl']['recipient_maps'] = 'Ontvanger-kaarten'; $lang['acl']['unlimited_quota'] = 'Onbeperkt quotum voor postvakken'; +$lang['acl']['extend_sender_acl'] = 'Sta verzenden via externe adressen toe'; $lang['acl']['prohibited'] = 'Toegang geweigerd'; +$lang['edit']['extended_sender_acl'] = 'Externe verzendadressen'; +$lang['edit']['extended_sender_acl_info'] = 'Wanneer mogelijk dient er een DKIM-sleutel geïmporteerd te worden. Vergeet niet om deze server toe te voegen aan het SPF-record
+ Zodra er een domein of aliasdomein wordt toegevoegd aan deze server, overeenkomend met een extern verzendadres, wordt het externe adres verwijderd.
+ Gebruik @domain.tld om verzenden vanuit *@domain.tld toe te staan.'; +$lang['edit']['sender_acl_info'] = 'Als postvakgebruiker A toegestaan is te verzenden als postvakgebruiker B, zal het verzendadres niet automatisch worden weergegeven in het "van"-veld in SOGo. Postvakgebruiker A dient hiervoor een aparte vermelding te maken in SOGo. Dit is niet van toepassing op aliasadressen.'; + $lang['mailbox']['quarantine_notification'] = 'Quarantaine-notificaties'; $lang['mailbox']['never'] = 'Nooit'; $lang['mailbox']['hourly'] = 'Ieder uur'; @@ -513,7 +528,11 @@ $lang['tfa']['tfa'] = "Tweefactorauthenticatie"; $lang['tfa']['set_tfa'] = "Kies tweefactorauthenticatie-methode"; $lang['tfa']['yubi_otp'] = "Yubico OTP"; $lang['tfa']['key_id'] = "Geef deze YubiKey een naam"; +$lang['tfa']['init_u2f'] = "Even geduld aub..."; +$lang['tfa']['start_u2f_validation'] = "Start validatie"; +$lang['tfa']['reload_retry'] = "- (herlaad de pagina als het probleem aanhoudt)"; $lang['tfa']['key_id_totp'] = "Geef deze key een naam"; +$lang['tfa']['error_code'] = "Foutcode"; $lang['tfa']['api_register'] = 'Mailcow maakt gebruik van de Yubico Cloud API. Om dit te benutten heeft u een API-sleutel van Yubico nodig, deze kunt u hier opvragen'; $lang['tfa']['u2f'] = "U2F"; $lang['tfa']['none'] = "Deactiveer"; @@ -611,7 +630,7 @@ $lang['admin']['time'] = 'Tijd'; $lang['admin']['last_applied'] = 'Voor het laatst toegepast'; $lang['admin']['reset_limit'] = 'Verwijder hash'; $lang['admin']['hash_remove_info'] = 'Het verwijderen van een ratelimit-hash, indien nog aanwezig, zal zijn teller volledig herstellen.
Elke hash wordt aangeduid met een aparte kleur.'; -$lang['warning']['hash_not_found'] = 'Hash niet gevonden, mogelijk is deze al verwijderd.'; +$lang['warning']['hash_not_found'] = 'Hash niet gevonden of reeds verwijderd'; $lang['success']['hash_deleted'] = 'Hash verwijderd'; $lang['admin']['authed_user'] = 'Geauthenticeerde gebruiker'; $lang['admin']['priority'] = 'Prioriteit'; @@ -738,7 +757,7 @@ $lang['quarantine']['action'] = "Handeling"; $lang['quarantine']['rcpt'] = "Ontvanger"; $lang['quarantine']['qid'] = "Rspamd QID"; $lang['quarantine']['sender'] = "Afzender"; -$lang['quarantine']['show_item'] = "Laat item zien"; +$lang['quarantine']['show_item'] = "Toon item"; $lang['quarantine']['check_hash'] = "Zoek bestandshash op in VT"; $lang['quarantine']['qitem'] = "Quarantaine-item"; $lang['quarantine']['subj'] = "Onderwerp"; @@ -759,6 +778,8 @@ $lang['danger']['spam_learn_error'] = "Spamtraining-fout: %s"; $lang['success']['qlearn_spam'] = "Bericht %s werd als spam gemarkeerd en is verwijderd"; $lang['debug']['system_containers'] = 'Systeem & containers'; +$lang['debug']['started_on'] = 'Gestart op'; +$lang['debug']['jvm_memory_solr'] = 'JVM-geheugengebruik'; $lang['debug']['solr_status'] = 'Status van Solr'; $lang['debug']['solr_dead'] = 'Solr is uitgeschakeld, uitgevallen of nog bezig met opstarten.'; $lang['debug']['logs'] = 'Logs'; @@ -858,3 +879,16 @@ $lang['admin']['validate_license_now'] = 'Valideer licentie'; $lang['admin']['customer_id'] = 'Klantnummer'; $lang['admin']['service_id'] = 'Servicenummer'; + +$lang['admin']['lookup_mx'] = 'Match bestemming aan MX (gebruik .outlook.com om alle mail gericht aan MX *.outlook.com over deze hop te laten gaan)'; +$lang['edit']['mbox_rl_info'] = 'Deze ratelimit wordt toegepast op de postvakgebruiker, en geldt voor elk "van"-adres gebruikt door de huidige postvakgebruiker. Een postvak-ratelimit gaat boven een domein-ratelimit.'; + +$lang['add']['relayhost_wrapped_tls_info'] = 'Gebruik geen in TLS-gewrapte poorten (meestal gebruikt op poort 465).
+ Gebruik elke niet-gewrapte poort en initieer STARTTLS. Beleid om verleuteling te forceren kan worden ingesteld bij "Globaal versleutelingsbeleid".'; + +$lang['admin']['transport_dest_format'] = 'Voorbeeld: example.org, .example.org, *, postvak@example.org (meerdere waarden zijn kommagescheiden)'; + +$lang['mailbox']['alias_domain_backupmx'] = 'Aliasdomein inactief voor doorstuurdomein'; + +$lang['danger']['extra_acl_invalid'] = 'Extern verzendadres "%s" is ongeldig'; +$lang['danger']['extra_acl_invalid_domain'] = 'Extern verzendadres "%s" gebruikt een ongeldig domein'; From 3c1532fe1761d53290ba7457a76cd49d002dc384 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 28 Sep 2019 19:43:52 +0200 Subject: [PATCH 4/7] [Compose] Update PHP image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9d3a4d91..9baa1d28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,7 +95,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.47 + image: mailcow/phpfpm:1.48 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: From 3811866ea09819cdea093caed20ed24976beb403 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 28 Sep 2019 19:58:40 +0200 Subject: [PATCH 5/7] [PHP-FPM] Add composer --- data/Dockerfiles/phpfpm/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index ea8502d7..b4c60308 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -63,6 +63,9 @@ RUN apk add -U --no-cache autoconf \ && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql soap sockets xmlrpc zip \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ + && curl --silent --show-error https://getcomposer.org/installer | php \ + && mv composer.phar /usr/local/bin/composer \ + && chmod +x /usr/local/bin/composer \ && apk del --purge autoconf \ cyrus-sasl-dev \ freetype-dev \ From 7a85abdb42c4aa0d92876717c56a053a5d01c94c Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 28 Sep 2019 20:00:04 +0200 Subject: [PATCH 6/7] [Web] Prepare for oauth2 [Web] Some lib updates [Web] Allow to add a footer --- data/web/admin.php | 4 + data/web/inc/footer.inc.php | 11 +- data/web/inc/functions.customize.inc.php | 3 + data/web/inc/functions.oauth2.inc.php | 208 ++++ data/web/inc/init_db.inc.php | 71 +- data/web/inc/lib/composer.json | 5 +- data/web/inc/lib/composer.lock | 131 ++- .../bshaffer/oauth2-server-php/CHANGELOG.md | 200 ++++ .../vendor/bshaffer/oauth2-server-php/LICENSE | 21 + .../bshaffer/oauth2-server-php/README.md | 8 + .../bshaffer/oauth2-server-php/composer.json | 36 + .../src/OAuth2/Autoloader.php | 54 + .../ClientAssertionTypeInterface.php | 28 + .../OAuth2/ClientAssertionType/HttpBasic.php | 139 +++ .../OAuth2/Controller/AuthorizeController.php | 480 ++++++++ .../AuthorizeControllerInterface.php | 58 + .../OAuth2/Controller/ResourceController.php | 156 +++ .../ResourceControllerInterface.php | 41 + .../src/OAuth2/Controller/TokenController.php | 333 ++++++ .../Controller/TokenControllerInterface.php | 39 + .../OAuth2/Encryption/EncryptionInterface.php | 34 + .../src/OAuth2/Encryption/FirebaseJwt.php | 47 + .../src/OAuth2/Encryption/Jwt.php | 223 ++++ .../OAuth2/GrantType/AuthorizationCode.php | 142 +++ .../OAuth2/GrantType/ClientCredentials.php | 98 ++ .../OAuth2/GrantType/GrantTypeInterface.php | 59 + .../src/OAuth2/GrantType/JwtBearer.php | 247 ++++ .../src/OAuth2/GrantType/RefreshToken.php | 154 +++ .../src/OAuth2/GrantType/UserCredentials.php | 123 ++ .../OpenID/Controller/AuthorizeController.php | 135 +++ .../AuthorizeControllerInterface.php | 12 + .../OpenID/Controller/UserInfoController.php | 62 + .../UserInfoControllerInterface.php | 30 + .../OpenID/GrantType/AuthorizationCode.php | 41 + .../OpenID/ResponseType/AuthorizationCode.php | 66 ++ .../AuthorizationCodeInterface.php | 27 + .../OpenID/ResponseType/CodeIdToken.php | 40 + .../ResponseType/CodeIdTokenInterface.php | 9 + .../OAuth2/OpenID/ResponseType/IdToken.php | 178 +++ .../OpenID/ResponseType/IdTokenInterface.php | 30 + .../OpenID/ResponseType/IdTokenToken.php | 45 + .../ResponseType/IdTokenTokenInterface.php | 9 + .../Storage/AuthorizationCodeInterface.php | 37 + .../OpenID/Storage/UserClaimsInterface.php | 35 + .../oauth2-server-php/src/OAuth2/Request.php | 252 ++++ .../src/OAuth2/RequestInterface.php | 39 + .../oauth2-server-php/src/OAuth2/Response.php | 487 ++++++++ .../src/OAuth2/ResponseInterface.php | 53 + .../src/OAuth2/ResponseType/AccessToken.php | 218 ++++ .../ResponseType/AccessTokenInterface.php | 33 + .../OAuth2/ResponseType/AuthorizationCode.php | 101 ++ .../AuthorizationCodeInterface.php | 30 + .../OAuth2/ResponseType/JwtAccessToken.php | 159 +++ .../ResponseType/ResponseTypeInterface.php | 13 + .../oauth2-server-php/src/OAuth2/Scope.php | 109 ++ .../src/OAuth2/ScopeInterface.php | 35 + .../oauth2-server-php/src/OAuth2/Server.php | 1019 +++++++++++++++++ .../OAuth2/Storage/AccessTokenInterface.php | 65 ++ .../Storage/AuthorizationCodeInterface.php | 86 ++ .../src/OAuth2/Storage/Cassandra.php | 660 +++++++++++ .../Storage/ClientCredentialsInterface.php | 49 + .../src/OAuth2/Storage/ClientInterface.php | 66 ++ .../src/OAuth2/Storage/CouchbaseDB.php | 331 ++++++ .../src/OAuth2/Storage/DynamoDB.php | 540 +++++++++ .../src/OAuth2/Storage/JwtAccessToken.php | 87 ++ .../Storage/JwtAccessTokenInterface.php | 14 + .../src/OAuth2/Storage/JwtBearerInterface.php | 74 ++ .../src/OAuth2/Storage/Memory.php | 381 ++++++ .../src/OAuth2/Storage/Mongo.php | 392 +++++++ .../src/OAuth2/Storage/MongoDB.php | 380 ++++++ .../src/OAuth2/Storage/Pdo.php | 731 ++++++++++++ .../src/OAuth2/Storage/PublicKeyInterface.php | 30 + .../src/OAuth2/Storage/Redis.php | 321 ++++++ .../OAuth2/Storage/RefreshTokenInterface.php | 82 ++ .../src/OAuth2/Storage/ScopeInterface.php | 46 + .../Storage/UserCredentialsInterface.php | 52 + .../src/OAuth2/TokenType/Bearer.php | 130 +++ .../src/OAuth2/TokenType/Mac.php | 22 + .../OAuth2/TokenType/TokenTypeInterface.php | 21 + .../test/OAuth2/AutoloadTest.php | 18 + .../Controller/AuthorizeControllerTest.php | 493 ++++++++ .../Controller/ResourceControllerTest.php | 177 +++ .../OAuth2/Controller/TokenControllerTest.php | 332 ++++++ .../OAuth2/Encryption/FirebaseJwtTest.php | 103 ++ .../test/OAuth2/Encryption/JwtTest.php | 103 ++ .../GrantType/AuthorizationCodeTest.php | 224 ++++ .../GrantType/ClientCredentialsTest.php | 160 +++ .../test/OAuth2/GrantType/ImplicitTest.php | 144 +++ .../test/OAuth2/GrantType/JwtBearerTest.php | 361 ++++++ .../OAuth2/GrantType/RefreshTokenTest.php | 205 ++++ .../OAuth2/GrantType/UserCredentialsTest.php | 173 +++ .../Controller/AuthorizeControllerTest.php | 183 +++ .../Controller/UserInfoControllerTest.php | 45 + .../GrantType/AuthorizationCodeTest.php | 58 + .../OpenID/ResponseType/CodeIdTokenTest.php | 183 +++ .../OpenID/ResponseType/IdTokenTest.php | 185 +++ .../OpenID/ResponseType/IdTokenTokenTest.php | 92 ++ .../OpenID/Storage/AuthorizationCodeTest.php | 95 ++ .../OAuth2/OpenID/Storage/UserClaimsTest.php | 41 + .../test/OAuth2/RequestTest.php | 117 ++ .../test/OAuth2/ResponseTest.php | 38 + .../OAuth2/ResponseType/AccessTokenTest.php | 108 ++ .../ResponseType/JwtAccessTokenTest.php | 178 +++ .../test/OAuth2/ScopeTest.php | 43 + .../test/OAuth2/ServerTest.php | 685 +++++++++++ .../test/OAuth2/Storage/AccessTokenTest.php | 102 ++ .../OAuth2/Storage/AuthorizationCodeTest.php | 106 ++ .../OAuth2/Storage/ClientCredentialsTest.php | 28 + .../test/OAuth2/Storage/ClientTest.php | 110 ++ .../test/OAuth2/Storage/DynamoDBTest.php | 40 + .../OAuth2/Storage/JwtAccessTokenTest.php | 41 + .../test/OAuth2/Storage/JwtBearerTest.php | 25 + .../test/OAuth2/Storage/PdoTest.php | 41 + .../test/OAuth2/Storage/PublicKeyTest.php | 29 + .../test/OAuth2/Storage/RefreshTokenTest.php | 41 + .../test/OAuth2/Storage/ScopeTest.php | 53 + .../OAuth2/Storage/UserCredentialsTest.php | 40 + .../test/OAuth2/TokenType/BearerTest.php | 59 + .../oauth2-server-php/test/bootstrap.php | 12 + .../oauth2-server-php/test/cleanup.php | 15 + .../oauth2-server-php/test/config/keys/id_rsa | 15 + .../test/config/keys/id_rsa.pub | 16 + .../test/config/storage.json | 188 +++ .../test/lib/OAuth2/Request/TestRequest.php | 66 ++ .../test/lib/OAuth2/Storage/BaseTest.php | 38 + .../test/lib/OAuth2/Storage/Bootstrap.php | 967 ++++++++++++++++ .../test/lib/OAuth2/Storage/NullStorage.php | 32 + .../vendor/composer/autoload_namespaces.php | 1 + .../lib/vendor/composer/autoload_static.php | 11 + .../inc/lib/vendor/composer/installed.json | 137 ++- .../inc/lib/vendor/ddeboer/imap/CHANGELOG.md | 43 + .../web/inc/lib/vendor/ddeboer/imap/README.md | 5 + .../inc/lib/vendor/ddeboer/imap/composer.json | 9 +- .../vendor/ddeboer/imap/src/Connection.php | 15 +- .../imap/src/Exception/AbstractException.php | 46 +- .../src/Exception/ImapFetchbodyException.php | 9 + .../Exception/ImapFetchheaderException.php | 9 + .../imap/src/Exception/ImapMsgnoException.php | 9 + .../src/Exception/ImapNumMsgException.php | 9 + .../src/Exception/ImapStatusException.php | 9 + .../Exception/MessageUndeleteException.php | 9 + .../ResourceCheckFailureException.php | 9 + .../vendor/ddeboer/imap/src/ImapResource.php | 18 +- .../lib/vendor/ddeboer/imap/src/Mailbox.php | 64 +- .../ddeboer/imap/src/MailboxInterface.php | 2 +- .../lib/vendor/ddeboer/imap/src/Message.php | 45 +- .../imap/src/Message/AbstractMessage.php | 48 +- .../ddeboer/imap/src/Message/AbstractPart.php | 82 +- .../ddeboer/imap/src/Message/Attachment.php | 9 +- .../imap/src/Message/AttachmentInterface.php | 2 +- .../ddeboer/imap/src/Message/EmailAddress.php | 4 +- .../imap/src/Message/EmbeddedMessage.php | 2 +- .../ddeboer/imap/src/Message/Headers.php | 5 +- .../ddeboer/imap/src/Message/Parameters.php | 8 +- .../imap/src/Message/PartInterface.php | 44 +- .../ddeboer/imap/src/Message/Transcoder.php | 498 ++++---- .../ddeboer/imap/src/MessageInterface.php | 5 + .../ddeboer/imap/src/Search/AbstractDate.php | 2 +- .../Search/LogicalOperator/OrConditions.php | 2 +- .../ddeboer/imap/src/SearchExpression.php | 2 +- .../lib/vendor/ddeboer/imap/src/Server.php | 28 +- .../php-mime-mail-parser/README.md | 9 +- .../php-mime-mail-parser/compile_mailparse.sh | 10 + .../php-mime-mail-parser/src/Attachment.php | 6 +- .../php-mime-mail-parser/src/Charset.php | 22 +- .../php-mime-mail-parser/src/MimePart.php | 4 +- .../php-mime-mail-parser/src/Parser.php | 12 +- .../vendor/robthree/twofactorauth/README.md | 10 +- .../twofactorauth/TwoFactorAuth.phpproj | 2 +- .../robthree/twofactorauth/composer.json | 2 +- ...ider.php => ImageChartsQRCodeProvider.php} | 8 +- .../twofactorauth/lib/TwoFactorAuth.php | 2 +- .../twofactorauth/tests/TwoFactorAuthTest.php | 2 +- data/web/inc/prerequisites.inc.php | 28 + data/web/lang/lang.de.php | 6 +- data/web/lang/lang.en.php | 4 +- 176 files changed, 18448 insertions(+), 504 deletions(-) create mode 100644 data/web/inc/functions.oauth2.inc.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/HttpBasic.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/FirebaseJwt.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/JwtBearer.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoController.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Response.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Scope.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php create mode 100755 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/TokenTypeInterface.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/AutoloadTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/cleanup.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php create mode 100755 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php create mode 100755 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php create mode 100644 data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapFetchbodyException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapFetchheaderException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapMsgnoException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapNumMsgException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapStatusException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/MessageUndeleteException.php create mode 100644 data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ResourceCheckFailureException.php create mode 100755 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh rename data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/{GoogleQRCodeProvider.php => ImageChartsQRCodeProvider.php} (78%) diff --git a/data/web/admin.php b/data/web/admin.php index 5c79aab4..155c6fc1 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -974,6 +974,10 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC +
+ + +
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index af087607..4c8a1fe4 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -174,7 +174,16 @@ $(document).ready(function() { } }); - + +
+
+ +
+ set('TITLE_NAME', htmlspecialchars($title_name)); $redis->set('MAIN_NAME', htmlspecialchars($main_name)); $redis->set('APPS_NAME', htmlspecialchars($apps_name)); $redis->set('HELP_TEXT', $help_text); + $redis->set('UI_IMPRESS', $ui_impress); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -201,6 +203,7 @@ function customize($_action, $_item, $_data = null) { $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI'; $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : 'mailcow Apps'; $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; + $data['ui_impress'] = ($ui_impress = $redis->get('UI_IMPRESS')) ? $ui_impress : false; return $data; } catch (RedisException $e) { diff --git a/data/web/inc/functions.oauth2.inc.php b/data/web/inc/functions.oauth2.inc.php new file mode 100644 index 00000000..fdc908b3 --- /dev/null +++ b/data/web/inc/functions.oauth2.inc.php @@ -0,0 +1,208 @@ + 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + switch ($_action) { + case 'add': + switch ($_type) { + case 'client': + $client_id = $_data['client_id']; + $client_secret = $_data['client_secret']; + $redirect_uri = $_data['redirect_uri']; + // $grant_type = isset($_data['grant_type']) ? $_data['grant_type'] : 'authorization_code'; + // $scope = isset($_data['scope']) ? $_data['scope'] : 'profile'; + if ($grant_type != "authorization_code" && $grant_type != "password") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + // For future use + if ($scope != "profile") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!ctype_alnum($client_id) || !ctype_alnum($client_secret)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("SELECT 'client' FROM `oauth_clients` + WHERE `client_id` = :client_id"); + $stmt->execute(array(':client_id' => $client_id)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Client ID exists' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `oauth_clients` (`client_id`, `client_secret` ,`redirect_uri`) + VALUES (:client_id, :client_secret, :redirect_uri)"); + $stmt->execute(array( + ':client_id' => $client_id, + ':client_secret' => $client_secret, + ':redirect_uri' => $redirect_uri + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Added client access' + ); + break; + } + break; + case 'edit': + switch ($_type) { + case 'client': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = oauth2('details', 'client', $id); + if (!empty($is_now)) { + $client_id = (!empty($_data['client_id'])) ? $_data['client_id'] : $is_now['client_id']; + $client_secret = (!empty($_data['client_secret'])) ? $_data['client_secret'] : $is_now['client_secret']; + $redirect_uri = (!empty($_data['redirect_uri'])) ? $_data['redirect_uri'] : $is_now['redirect_uri']; + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!ctype_alnum($client_id) || !ctype_alnum($client_secret)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Client ID and secret must be alphanumeric' + ); + return false; + } + if (empty($redirect_uri)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redirect/Callback URL cannot be empty' + ); + return false; + } + $stmt = $pdo->prepare("UPDATE `oauth_clients` SET + `client_id` = :client_id, + `client_secret` = :client_secret, + `redirect_uri` = :redirect_uri + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':client_id' => $client_id, + ':client_secret' => $client_secret, + ':redirect_uri' => $redirect_uri + )); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars(implode(', ', $ids))) + ); + break; + } + break; + case 'delete': + switch ($_type) { + case 'client': + (array)$ids = $_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_clients` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', $ids)) + ); + break; + case 'access_token': + (array)$access_tokens = $_data['access_token']; + foreach ($access_tokens as $access_token) { + if (!ctype_alnum($access_token)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` WHERE `access_token` = :access_token"); + $stmt->execute(array( + ':access_token' => $access_token + )); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', $access_tokens)) + ); + break; + case 'refresh_token': + (array)$refresh_tokens = $_data['refresh_token']; + foreach ($refresh_tokens as $refresh_token) { + if (!ctype_alnum($refresh_token)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `refresh_token` = :refresh_token"); + $stmt->execute(array( + ':refresh_token' => $refresh_token + )); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', $refresh_tokens)) + ); + break; + } + break; + case 'get': + switch ($_type) { + case 'clients': + $stmt = $pdo->query("SELECT `id` FROM `oauth_clients`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $oauth_clients[] = $row['id']; + } + return $oauth_clients; + break; + } + break; + case 'details': + switch ($_type) { + case 'client': + $stmt = $pdo->prepare("SELECT * FROM `oauth_clients` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $oauth_client_details = $stmt->fetch(PDO::FETCH_ASSOC); + return $oauth_client_details; + break; + } + break; + } +} \ No newline at end of file diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 91672e3b..c97eeb56 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "22092019_0940"; + $db_version = "27092019_1040"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -330,7 +330,7 @@ function init_db_schema() { "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", - "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", + "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '0'", "sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'", @@ -783,6 +783,73 @@ function init_db_schema() { ) ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_clients" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "client_id" => "VARCHAR(80) NOT NULL", + "client_secret" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "grant_types" => "VARCHAR(80)", + "scope" => "VARCHAR(4000)", + "user_id" => "VARCHAR(80)" + ), + "keys" => array( + "primary" => array( + "" => array("client_id") + ), + "unique" => array( + "id" => array("id") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_access_tokens" => array( + "cols" => array( + "access_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("access_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_authorization_codes" => array( + "cols" => array( + "authorization_code" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "redirect_uri" => "VARCHAR(2000)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)", + "id_token" => "VARCHAR(1000)" + ), + "keys" => array( + "primary" => array( + "" => array("authorization_code") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), + "oauth_refresh_tokens" => array( + "cols" => array( + "refresh_token" => "VARCHAR(40) NOT NULL", + "client_id" => "VARCHAR(80) NOT NULL", + "user_id" => "VARCHAR(80)", + "expires" => "TIMESTAMP NOT NULL", + "scope" => "VARCHAR(4000)" + ), + "keys" => array( + "primary" => array( + "" => array("refresh_token") + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ) ); diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 07dfe4ba..d733ef82 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -3,9 +3,10 @@ "robthree/twofactorauth": "^1.6", "yubico/u2flib-server": "^1.0", "phpmailer/phpmailer": "^5.2", - "php-mime-mail-parser/php-mime-mail-parser": "^5.1", + "php-mime-mail-parser/php-mime-mail-parser": "^5.0.5", "soundasleep/html2text": "^0.5.0", "ddeboer/imap": "^1.5", - "matthiasmullie/minify": "^1.3" + "matthiasmullie/minify": "^1.3", + "bshaffer/oauth2-server-php": "^1.11" } } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index b2d6defe..aec41836 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,20 +4,78 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e72f119b7f62fea0aa6123109abb9a35", + "content-hash": "c435e39c1b3cbefbecebc3dc6fcc8d89", "packages": [ { - "name": "ddeboer/imap", - "version": "1.6.0", + "name": "bshaffer/oauth2-server-php", + "version": "v1.11.1", "source": { "type": "git", - "url": "https://github.com/ddeboer/imap.git", - "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede" + "url": "https://github.com/bshaffer/oauth2-server-php.git", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ddeboer/imap/zipball/4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", - "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^4.0", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master" + }, + "suggest": { + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage", + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage" + }, + "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": "2018-12-04T00:29:32+00:00" + }, + { + "name": "ddeboer/imap", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "ff985d72916267cba2f944e7c9ee654c69893219" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/ff985d72916267cba2f944e7c9ee654c69893219", + "reference": "ff985d72916267cba2f944e7c9ee654c69893219", "shasum": "" }, "require": { @@ -27,10 +85,11 @@ "php": "^7.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.13", - "phpstan/phpstan": "^0.9.1", - "phpstan/phpstan-phpunit": "^0.9.3", - "phpunit/phpunit": "^7.4", + "friendsofphp/php-cs-fixer": "^2.14", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpunit": "^7.5", "zendframework/zend-mail": "^2.10" }, "type": "library", @@ -63,7 +122,7 @@ "imap", "mail" ], - "time": "2018-12-04T13:35:19+00:00" + "time": "2019-04-15T09:18:52+00:00" }, { "name": "matthiasmullie/minify", @@ -221,31 +280,31 @@ }, { "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "2.11.1", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af" + "reference": "27983433aabeccee832573c3c56e6a4855e57745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/4769e942ed0dbbdd7882fc390b119d625463c8af", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/27983433aabeccee832573c3c56e6a4855e57745", + "reference": "27983433aabeccee832573c3c56e6a4855e57745", "shasum": "" }, "require": { "ext-mailparse": "*", - "php": "^5.4.0 || ^7.0" + "php": "^7.1" }, "replace": { "exorus/php-mime-mail-parser": "*", "messaged/php-mime-mail-parser": "*" }, "require-dev": { - "phpunit/php-token-stream": "^1.3.0", - "phpunit/phpunit": "^4.0 || ^5.0", - "satooshi/php-coveralls": "0.*", - "squizlabs/php_codesniffer": "2.*" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/php-token-stream": "^3.0", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.4" }, "type": "library", "autoload": { @@ -258,12 +317,6 @@ "MIT" ], "authors": [ - { - "name": "bucabay", - "email": "gabe@fijiwebdesign.com", - "homepage": "http://www.fijiwebdesign.com", - "role": "Developer" - }, { "name": "eXorus", "email": "exorus.spam@gmail.com", @@ -287,17 +340,25 @@ "email": "alkne@gmail.com", "homepage": "https://code.google.com/p/php-mime-mail-parser", "role": "Developer" + }, + { + "name": "bucabay", + "email": "gabe@fijiwebdesign.com", + "homepage": "http://www.fijiwebdesign.com", + "role": "Developer" } ], - "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+", + "description": "A fully tested email parser for PHP 7.1+ (mailparse extension wrapper).", "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", "keywords": [ "MimeMailParser", "mail", "mailparse", - "mime" + "mime", + "parser", + "php" ], - "time": "2018-04-30T05:55:59+00:00" + "time": "2019-09-23T11:57:58+00:00" }, { "name": "phpmailer/phpmailer", @@ -378,16 +439,16 @@ }, { "name": "robthree/twofactorauth", - "version": "1.6.5", + "version": "1.6.7", "source": { "type": "git", "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2" + "reference": "3407c33775391fa8c36f7d766f26c5e59a736374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/f5f58a4c62d0336a0e6175856894a51f3565dad2", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/3407c33775391fa8c36f7d766f26c5e59a736374", + "reference": "3407c33775391fa8c36f7d766f26c5e59a736374", "shasum": "" }, "require": { @@ -425,7 +486,7 @@ "php", "tfa" ], - "time": "2018-06-09T10:09:59+00:00" + "time": "2019-06-21T08:51:04+00:00" }, { "name": "soundasleep/html2text", diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md new file mode 100644 index 00000000..1b87f3da --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/CHANGELOG.md @@ -0,0 +1,200 @@ +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.10.0 (2017-11-15) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/889 + + * #795 - [feature] added protected createPayload method to allow easier customization of JWT payload + * #807 - [refactor] simplifies UserInfoController constructor + * #814 - [docs] Adds https to README link + * #827 - [testing] Explicitly pulls in phpunit 4 + * #828 - [docs] PHPDoc improvements and type hinting of variables. + * #829 - [bug] Fix CORS issue for revoking and requesting an access token + * #869 - [testing] Remove php 5.3 from travis and use vendored phpunit + * #834 - [feature] use random_bytes if available + * #851 - [docs] Fix PHPDoc + * #872 - [bug] Fix count() error on PHP 7.2 + * #873 - [testing] adds php 7.2 to travis + * #794 - [docs] Fix typo in composer.json + * #885 - [testing] Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase + +* 1.9.0 (2017-01-06) + + PR: https://github.com/bshaffer/oauth2-server-php/pull/788 + + * bug #645 - Allow null for client_secret + * bug #651 - Fix bug in isPublicClient of Cassandra Storage + * bug #670 - Bug in client's scope restriction + * bug #672 - Implemented method to override the password hashing algorithm + * bug #698 - Fix Token Response's Content-Type to application/json + * bug #729 - Ensures unsetAccessToken and unsetRefreshToken return a bool + * bug #749 - Fix UserClaims for CodeIdToken + * bug #784 - RFC6750 compatibility + * bug #776 - Fix "redirect_uri_mismatch" for URIs with encoded characters + * bug #759 - no access token supplied to resource controller results in empty request body + * bug #773 - Use OpenSSL random method before attempting Mcrypt's. + * bug #790 - Add mongo db + +* 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 diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE new file mode 100644 index 00000000..d7ece846 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/LICENSE @@ -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. diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md new file mode 100644 index 00000000..117743d4 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md @@ -0,0 +1,8 @@ +oauth2-server-php +================= + +[![Build Status](https://travis-ci.org/bshaffer/oauth2-server-php.svg?branch=master)](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](https://bshaffer.github.io/oauth2-server-php-docs/) diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json new file mode 100644 index 00000000..272d2002 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json @@ -0,0 +1,36 @@ +{ + "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", + "autoload": { + "psr-0": { "OAuth2": "src/" } + }, + "require":{ + "php":">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master", + "mongodb/mongodb": "^1.1" + }, + "suggest": { + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage", + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage" + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php new file mode 100644 index 00000000..4ec08cbd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php @@ -0,0 +1,54 @@ + + * @license MIT License + */ +class Autoloader +{ + /** + * @var string + */ + private $dir; + + /** + * @param string $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; + } + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php new file mode 100644 index 00000000..6a167da9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/ClientAssertionTypeInterface.php @@ -0,0 +1,28 @@ + + */ +class HttpBasic implements ClientAssertionTypeInterface +{ + private $clientData; + + protected $storage; + protected $config; + + /** + * Config array $config should look as follows: + * @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 + * ); + * @endcode + * + * @param ClientCredentialsInterface $storage Storage + * @param array $config Configuration options for the server + */ + 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); + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed + * @throws LogicException + */ + 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; + } + + /** + * Get the client id + * + * @return mixed + */ + 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. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array|null 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php new file mode 100644 index 00000000..4bafb1d2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php @@ -0,0 +1,480 @@ + 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 + * ); + * @endcode + */ + 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; + } + + /** + * Handle the authorization request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param boolean $is_authorized + * @param mixed $user_id + * @return mixed|void + * @throws InvalidArgumentException + */ + 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); + } + + /** + * Set not authorized response + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param string $redirect_uri + * @param mixed $user_id + */ + 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 + * + * @TODO: add dependency injection for the parameters in this method + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param mixed $user_id + * @return array + */ + 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; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + 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 response type + $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 ((empty($clientScope) && !$this->scopeUtil->scopeExists($requestedScope)) + || (!empty($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 string $uri An absolute URI. + * @param array $params Parameters to be append as GET. + * + * @return string + * 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 the uri 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 + * @return bool + * @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 { + $registered_uri_length = strlen($registered_uri); + if ($registered_uri_length === 0) { + return false; + } + + // 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, $registered_uri_length), $registered_uri) === 0) { + return true; + } + } + } + + return false; + } + + /** + * Convenience method to access the scope + * + * @return string + */ + public function getScope() + { + return $this->scope; + } + + /** + * Convenience method to access the state + * + * @return int + */ + public function getState() + { + return $this->state; + } + + /** + * Convenience method to access the client id + * + * @return mixed + */ + public function getClientId() + { + return $this->client_id; + } + + /** + * Convenience method to access the redirect url + * + * @return string + */ + public function getRedirectUri() + { + return $this->redirect_uri; + } + + /** + * Convenience method to access the response type + * + * @return string + */ + public function getResponseType() + { + return $this->response_type; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php new file mode 100644 index 00000000..f758f976 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeControllerInterface.php @@ -0,0 +1,58 @@ +somehowDetermineUserId(); + * $is_authorized = $this->somehowDetermineUserAuthorization(); + * $response = new OAuth2\Response(); + * $authorizeController->handleAuthorizeRequest( + * OAuth2\Request::createFromGlobals(), + * $response, + * $is_authorized, + * $user_id + * ); + * $response->send(); + * @endcode + */ +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'; + + /** + * Handle the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param $is_authorized + * @param null $user_id + * @return mixed + */ + public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null); + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php new file mode 100644 index 00000000..926f90fd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php @@ -0,0 +1,156 @@ +tokenType = $tokenType; + $this->tokenStorage = $tokenStorage; + + $this->config = array_merge(array( + 'www_realm' => 'Service', + ), $config); + + if (is_null($scopeUtil)) { + $scopeUtil = new Scope(); + } + $this->scopeUtil = $scopeUtil; + } + + /** + * Verify the resource request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param null $scope + * @return bool + */ + 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; + } + + /** + * Get access token data. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array|null + */ + 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, 'invalid_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. + * + * @return array + */ + public function getToken() + { + return $this->token; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php new file mode 100644 index 00000000..0e847ca6 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php @@ -0,0 +1,41 @@ +verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) { + * $response->send(); // authorization failed + * die(); + * } + * return json_encode($resource); // valid token! Send the stuff! + * @endcode + */ +interface ResourceControllerInterface +{ + /** + * Verify the resource request + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @param string $scope + * @return mixed + */ + public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null); + + /** + * Get access token data. + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return mixed + */ + public function getAccessTokenData(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php new file mode 100644 index 00000000..7fdaf85a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php @@ -0,0 +1,333 @@ + + */ + protected $grantTypes; + + /** + * @var ClientAssertionTypeInterface + */ + protected $clientAssertionType; + + /** + * @var ScopeInterface + */ + protected $scopeUtil; + + /** + * @var ClientInterface + */ + protected $clientStorage; + + /** + * Constructor + * + * @param AccessTokenInterface $accessToken + * @param ClientInterface $clientStorage + * @param array $grantTypes + * @param ClientAssertionTypeInterface $clientAssertionType + * @param ScopeInterface $scopeUtil + * @throws InvalidArgumentException + */ + 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; + } + + /** + * Handle the token request. + * + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + */ + 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', + 'Content-Type' => 'application/json' + )); + } + } + + /** + * 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 RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * + * @return bool|null|array + * + * @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')) === 'options') { + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + 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, OPTIONS')); + + 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; + } + + /** @var GrantTypeInterface $grantType */ + $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); + } + + /** + * Add grant type + * + * @param GrantTypeInterface $grantType - the grant type to add for the specified identifier + * @param string|null $identifier - 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; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + */ + 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 + * @throws RuntimeException + * @return bool|null + */ + public function revokeToken(RequestInterface $request, ResponseInterface $response) + { + if (strtolower($request->server('REQUEST_METHOD')) === 'options') { + $response->addHttpHeaders(array('Allow' => 'POST, OPTIONS')); + + return null; + } + + 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, OPTIONS')); + + 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php new file mode 100644 index 00000000..2f83ce4b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenControllerInterface.php @@ -0,0 +1,39 @@ +handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response()); + * $response->send(); + * @endcode + */ +interface TokenControllerInterface +{ + /** + * Handle the token request + * + * @param RequestInterface $request - The current http request + * @param ResponseInterface $response - An instance of OAuth2\ResponseInterface to contain the response data + */ + public function handleTokenRequest(RequestInterface $request, ResponseInterface $response); + + /** + * 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 RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * + * @return mixed + */ + public function grantAccessToken(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php new file mode 100644 index 00000000..8dc720a4 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/EncryptionInterface.php @@ -0,0 +1,34 @@ + + */ +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); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php new file mode 100644 index 00000000..c258b8fc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/Jwt.php @@ -0,0 +1,223 @@ +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); + } + + /** + * @param string $jwt + * @param null $key + * @param array|bool $allowedAlgorithms + * @return bool|mixed + */ + 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; + } + + /** + * @param $signature + * @param $input + * @param $key + * @param string $algo + * @return bool + * @throws InvalidArgumentException + */ + 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."); + } + } + + /** + * @param $input + * @param $key + * @param string $algo + * @return string + * @throws Exception + */ + 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."); + } + } + + /** + * @param $input + * @param $key + * @param string $algo + * @return mixed + * @throws Exception + */ + private function generateRSASignature($input, $key, $algo) + { + if (!openssl_sign($input, $signature, $key, $algo)) { + throw new Exception("Unable to sign data."); + } + + return $signature; + } + + /** + * @param string $data + * @return string + */ + public function urlSafeB64Encode($data) + { + $b64 = base64_encode($data); + $b64 = str_replace(array('+', '/', "\r", "\n", '='), + array('-', '_'), + $b64); + + return $b64; + } + + /** + * @param string $b64 + * @return mixed|string + */ + 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, + ); + } + + /** + * @param string $a + * @param string $b + * @return bool + */ + 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; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php new file mode 100644 index 00000000..784f6b3a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php @@ -0,0 +1,142 @@ + + */ +class AuthorizationCode implements GrantTypeInterface +{ + /** + * @var AuthorizationCodeInterface + */ + protected $storage; + + /** + * @var array + */ + protected $authCode; + + /** + * @param AuthorizationCodeInterface $storage - REQUIRED Storage class for retrieving authorization code information + */ + public function __construct(AuthorizationCodeInterface $storage) + { + $this->storage = $storage; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'authorization_code'; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + * @throws Exception + */ + 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')) != urldecode($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; + } + + /** + * Get the client id + * + * @return mixed + */ + public function getClientId() + { + return $this->authCode['client_id']; + } + + /** + * Get the scope + * + * @return string + */ + public function getScope() + { + return isset($this->authCode['scope']) ? $this->authCode['scope'] : null; + } + + /** + * Get the user id + * + * @return mixed + */ + public function getUserId() + { + return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php new file mode 100644 index 00000000..e135c2dd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/ClientCredentials.php @@ -0,0 +1,98 @@ + + * + * @see HttpBasic + */ +class ClientCredentials extends HttpBasic implements GrantTypeInterface +{ + /** + * @var array + */ + private $clientData; + + /** + * @param ClientCredentialsInterface $storage + * @param array $config + */ + 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); + } + + /** + * Get query string identifier + * + * @return string + */ + public function getQueryStringIdentifier() + { + return 'client_credentials'; + } + + /** + * Get scope + * + * @return string|null + */ + public function getScope() + { + $this->loadClientData(); + + return isset($this->clientData['scope']) ? $this->clientData['scope'] : null; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + $this->loadClientData(); + + return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + 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()); + } + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php new file mode 100644 index 00000000..f45786ff --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/GrantTypeInterface.php @@ -0,0 +1,59 @@ + + */ +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 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|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 string - The string identifier for grant_type. + * + * @see GrantTypeInterface::getQueryStringIdentifier() + */ + public function getQueryStringIdentifier() + { + return 'urn:ietf:params:oauth:grant-type:jwt-bearer'; + } + + /** + * Validates the data from the decoded JWT. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.@see 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; + } + + /** + * Get client id + * + * @return mixed + */ + public function getClientId() + { + return $this->jwt['iss']; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + return $this->jwt['sub']; + } + + /** + * Get scope + * + * @return null + */ + 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 GrantTypeInterface::createAccessToken() + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + $includeRefreshToken = false; + + return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php new file mode 100644 index 00000000..75c611f1 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/RefreshToken.php @@ -0,0 +1,154 @@ + + */ +class RefreshToken implements GrantTypeInterface +{ + /** + * @var array + */ + private $refreshToken; + + /** + * @var RefreshTokenInterface + */ + protected $storage; + + /** + * @var array + */ + protected $config; + + /** + * @param 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 + * ); + * @endcode + */ + 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; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'refresh_token'; + } + + /** + * Validate the OAuth request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null + */ + 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; + } + + /** + * Get client id + * + * @return mixed + */ + public function getClientId() + { + return $this->refreshToken['client_id']; + } + + /** + * Get user id + * + * @return mixed|null + */ + public function getUserId() + { + return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null; + } + + /** + * Get scope + * + * @return null|string + */ + public function getScope() + { + return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php new file mode 100644 index 00000000..b10c2dd0 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/UserCredentials.php @@ -0,0 +1,123 @@ + + */ +class UserCredentials implements GrantTypeInterface +{ + /** + * @var array + */ + private $userInfo; + + /** + * @var UserCredentialsInterface + */ + protected $storage; + + /** + * @param UserCredentialsInterface $storage - REQUIRED Storage class for retrieving user credentials information + */ + public function __construct(UserCredentialsInterface $storage) + { + $this->storage = $storage; + } + + /** + * @return string + */ + public function getQueryStringIdentifier() + { + return 'password'; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool|mixed|null + * + * @throws LogicException + */ + 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; + } + + /** + * Get client id + * + * @return mixed|null + */ + public function getClientId() + { + return null; + } + + /** + * Get user id + * + * @return mixed + */ + public function getUserId() + { + return $this->userInfo['user_id']; + } + + /** + * Get scope + * + * @return null|string + */ + public function getScope() + { + return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null; + } + + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope) + { + return $accessToken->createAccessToken($client_id, $user_id, $scope); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php new file mode 100644 index 00000000..54c5f9a6 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php @@ -0,0 +1,135 @@ +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); + } + + /** + * @TODO: add dependency injection for the parameters in this method + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param mixed $user_id + * @return array + */ + 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; + } + + /** + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + 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; + } + + /** + * Array of valid response types + * + * @return array + */ + 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 string $request_scope - A space-separated string of scopes. + * @return boolean - 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); + } + + /** + * @return mixed + */ + public function getNonce() + { + return $this->nonce; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php new file mode 100644 index 00000000..b4967c31 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeControllerInterface.php @@ -0,0 +1,12 @@ +userClaimsStorage = $userClaimsStorage; + } + + /** + * Handle the user info request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + 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); + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php new file mode 100644 index 00000000..88e9228d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoControllerInterface.php @@ -0,0 +1,30 @@ +handleUserInfoRequest( + * OAuth2\Request::createFromGlobals(), + * $response + * ); + * $response->send(); + * @endcode + */ +interface UserInfoControllerInterface +{ + /** + * Handle user info request + * + * @param RequestInterface $request + * @param ResponseInterface $response + */ + public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php new file mode 100644 index 00000000..ee113a0e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/GrantType/AuthorizationCode.php @@ -0,0 +1,41 @@ + + */ +class AuthorizationCode extends BaseAuthorizationCode +{ + /** + * Create access token + * + * @param AccessTokenInterface $accessToken + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user id associated with the access token + * @param string $scope - scopes to be stored in space-separated string. + * @return array + */ + 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php new file mode 100644 index 00000000..b8ad41ff --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php @@ -0,0 +1,66 @@ + + */ +class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface +{ + /** + * Constructor + * + * @param AuthorizationCodeStorageInterface $storage + * @param array $config + */ + public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array()) + { + parent::__construct($storage, $config); + } + + /** + * @param $params + * @param null $user_id + * @return array + */ + 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 mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $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 string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @param string $id_token - OPTIONAL The OpenID Connect id_token. + * + * @return 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, $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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php new file mode 100644 index 00000000..eb94ef07 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCodeInterface.php @@ -0,0 +1,27 @@ + + */ +interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface +{ + /** + * Handle the creation of the authorization code. + * + * @param mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $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 string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @param string $id_token - OPTIONAL The OpenID Connect id_token. + * @return 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, $id_token = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php new file mode 100644 index 00000000..2696ada3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdToken.php @@ -0,0 +1,40 @@ +authCode = $authCode; + $this->idToken = $idToken; + } + + /** + * @param array $params + * @param mixed $user_id + * @return mixed + */ + public function getAuthorizeResponse($params, $user_id = null) + { + $result = $this->authCode->getAuthorizeResponse($params, $user_id); + $resultIdToken = $this->idToken->getAuthorizeResponse($params, $user_id); + $result[1]['query']['id_token'] = $resultIdToken[1]['fragment']['id_token']; + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php new file mode 100644 index 00000000..629adcca --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/CodeIdTokenInterface.php @@ -0,0 +1,9 @@ +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); + } + + /** + * @param array $params + * @param null $userInfo + * @return array|mixed + */ + 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); + } + + /** + * Create id token + * + * @param string $client_id + * @param mixed $userInfo + * @param mixed $nonce + * @param mixed $userClaims + * @param mixed $access_token + * @return mixed|string + */ + 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); + } + + /** + * @param $access_token + * @param null $client_id + * @return mixed|string + */ + 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, true); + $at_hash = substr($hash, 0, strlen($hash) / 2); + + return $this->encryptionUtil->urlSafeB64Encode($at_hash); + } + + /** + * @param array $token + * @param null $client_id + * @return mixed|string + */ + 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); + } + + /** + * @param $userInfo + * @return array + * @throws LogicException + */ + 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); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php new file mode 100644 index 00000000..226a3bcb --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenInterface.php @@ -0,0 +1,30 @@ +accessToken = $accessToken; + $this->idToken = $idToken; + } + + /** + * @param array $params + * @param mixed $user_id + * @return mixed + */ + 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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php new file mode 100644 index 00000000..ac13e203 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdTokenTokenInterface.php @@ -0,0 +1,9 @@ + + */ +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 string $code - authorization code to be stored. + * @param mixed $client_id - client identifier to be stored. + * @param mixed $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); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php new file mode 100644 index 00000000..9c5e7c8c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/UserClaimsInterface.php @@ -0,0 +1,35 @@ + value format. + * + * @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ + public function getUserClaims($user_id, $scope); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php new file mode 100644 index 00000000..f547bf6e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php @@ -0,0 +1,252 @@ +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 + * @param array $headers - The headers + * + * @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; + + if ($headers === null) { + $headers = array(); + } + + $this->headers = $headers + $this->getHeadersFromServer($this->server); + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function query($name, $default = null) + { + return isset($this->query[$name]) ? $this->query[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function request($name, $default = null) + { + return isset($this->request[$name]) ? $this->request[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function server($name, $default = null) + { + return isset($this->server[$name]) ? $this->server[$name] : $default; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function headers($name, $default = null) + { + $headers = array_change_key_case($this->headers); + $name = strtolower($name); + + return isset($headers[$name]) ? $headers[$name] : $default; + } + + /** + * @return array + */ + 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. + * + * @throws LogicException + */ + 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; + } + + /** + * @param array $server + * @return array + */ + 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(); + + /** @var Request $request */ + $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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php new file mode 100644 index 00000000..1d036b73 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/RequestInterface.php @@ -0,0 +1,39 @@ + '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', + ); + + /** + * @param array $parameters + * @param int $statusCode + * @param array $headers + */ + 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); + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * @param int $statusCode + * @param string $text + * @throws InvalidArgumentException + */ + 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); + } + + /** + * @return string + */ + public function getStatusText() + { + return $this->statusText; + } + + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param array $parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param array $parameters + */ + public function addParameters(array $parameters) + { + $this->parameters = array_merge($this->parameters, $parameters); + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getParameter($name, $default = null) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : $default; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setParameter($name, $value) + { + $this->parameters[$name] = $value; + } + + /** + * @param array $httpHeaders + */ + public function setHttpHeaders(array $httpHeaders) + { + $this->httpHeaders = $httpHeaders; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setHttpHeader($name, $value) + { + $this->httpHeaders[$name] = $value; + } + + /** + * @param array $httpHeaders + */ + public function addHttpHeaders(array $httpHeaders) + { + $this->httpHeaders = array_merge($this->httpHeaders, $httpHeaders); + } + + /** + * @return array + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getHttpHeader($name, $default = null) + { + return isset($this->httpHeaders[$name]) ? $this->httpHeaders[$name] : $default; + } + + /** + * @param string $format + * @return mixed + * @throws InvalidArgumentException + */ + public function getResponseBody($format = 'json') + { + switch ($format) { + case 'json': + return $this->parameters ? json_encode($this->parameters) : ''; + case 'xml': + // this only works for single-level arrays + $xml = new \SimpleXMLElement(''); + foreach ($this->parameters as $key => $param) { + $xml->addChild($key, $param); + } + + return $xml->asXML(); + } + + throw new InvalidArgumentException(sprintf('The format %s is not supported', $format)); + + } + + /** + * @param string $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); + } + + /** + * @param int $statusCode + * @param string $error + * @param string $errorDescription + * @param string $errorUri + * @return mixed + * @throws InvalidArgumentException + */ + 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)); + } + } + + /** + * @param int $statusCode + * @param string $url + * @param string $state + * @param string $error + * @param string $errorDescription + * @param string $errorUri + * @return mixed + * @throws InvalidArgumentException + */ + 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']) && !empty($parts['query']) ? '&' : '?'; + $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)); + } + } + + /** + * @return Boolean + * + * @api + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + */ + 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; + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param array $headers + * @return string + */ + 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; + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param string $name + * @return mixed + */ + private function beautifyHeaderName($name) + { + return preg_replace_callback('/\-(.)/', array($this, 'beautifyCallback'), ucfirst($name)); + } + + /** + * Function from Symfony2 HttpFoundation - output pretty header + * + * @param array $match + * @return string + */ + private function beautifyCallback($match) + { + return '-'.strtoupper($match[1]); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php new file mode 100644 index 00000000..fe920864 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php @@ -0,0 +1,53 @@ + + */ +class AccessToken implements AccessTokenInterface +{ + /** + * @var AccessTokenInterface + */ + protected $tokenStorage; + + /** + * @var RefreshTokenInterface + */ + protected $refreshStorage; + + /** + * @var array + */ + protected $config; + + /** + * @param AccessTokenStorageInterface $tokenStorage - REQUIRED Storage class for saving access token information + * @param 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); + } + + /** + * Get authorize response + * + * @param array $params + * @param mixed $user_id + * @return array + */ + 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 mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user ID associated with the access token + * @param string $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 + * @return array + * + * @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 string - A unique access token. + * + * @ingroup oauth2_section_4 + */ + protected function generateAccessToken() + { + if (function_exists('random_bytes')) { + $randomData = random_bytes(20); + 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 (function_exists('mcrypt_create_iv')) { + $randomData = mcrypt_create_iv(20, MCRYPT_DEV_URANDOM); + 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 string - A unique refresh token. + * + * @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 + * @throws RuntimeException + * @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; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php new file mode 100644 index 00000000..0e576df5 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessTokenInterface.php @@ -0,0 +1,33 @@ + + */ +interface AccessTokenInterface extends ResponseTypeInterface +{ + /** + * Handle the creation of access token, also issue refresh token if supported / desirable. + * + * @param mixed $client_id - client identifier related to the access token. + * @param mixed $user_id - user ID associated with the access token + * @param string $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); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php new file mode 100644 index 00000000..b92c73cd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php @@ -0,0 +1,101 @@ + + */ +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('random_bytes')) { + $randomData = random_bytes(100); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $randomData = openssl_random_pseudo_bytes(100); + } elseif (function_exists('mcrypt_create_iv')) { + $randomData = mcrypt_create_iv(100, MCRYPT_DEV_URANDOM); + } 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); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php new file mode 100644 index 00000000..4f0a29df --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCodeInterface.php @@ -0,0 +1,30 @@ + + */ +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 mixed $client_id - Client identifier related to the authorization code + * @param mixed $user_id - User ID associated with the authorization code + * @param string $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 string $scope - OPTIONAL Scopes to be stored in space-separated string. + * @return 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); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php new file mode 100644 index 00000000..0ee3708a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php @@ -0,0 +1,159 @@ + + */ +class JwtAccessToken extends AccessToken +{ + protected $publicKeyStorage; + protected $encryptionUtil; + + /** + * @param PublicKeyInterface $publicKeyStorage - + * @param AccessTokenStorageInterface $tokenStorage - + * @param RefreshTokenInterface $refreshStorage - + * @param array $config - array with key store_encrypted_token_string (bool true) + * whether the entire encrypted string is stored, + * or just the token ID is stored + * @param EncryptionInterface $encryptionUtil - + */ + 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 mixed $client_id - Client identifier related to the access token. + * @param mixed $user_id - User ID associated with the access token + * @param string $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 + * @return array - The access token + * + * @see http://tools.ietf.org/html/rfc6749#section-5 + * @ingroup oauth2_section_5 + */ + public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true) + { + // payload to encrypt + $payload = $this->createPayload($client_id, $user_id, $scope); + + /* + * Encode the payload data into a single JWT access_token string + */ + $access_token = $this->encodeToken($payload, $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 : $payload['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; + } + + /** + * @param array $token + * @param mixed $client_id + * @return mixed + */ + 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); + } + + /** + * This function can be used to create custom JWT payloads + * + * @param mixed $client_id - Client identifier related to the access token. + * @param mixed $user_id - User ID associated with the access token + * @param string $scope - (optional) Scopes to be stored in space-separated string. + * @return array - The access token + */ + protected function createPayload($client_id, $user_id, $scope = null) + { + // token to encrypt + $expires = time() + $this->config['access_lifetime']; + $id = $this->generateAccessToken(); + + $payload = 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 + ); + + if (isset($this->config['jwt_extra_payload_callable'])) { + if (!is_callable($this->config['jwt_extra_payload_callable'])) { + throw new \InvalidArgumentException('jwt_extra_payload_callable is not callable'); + } + + $extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope); + + if (!is_array($extra)) { + throw new \InvalidArgumentException('jwt_extra_payload_callable must return array'); + } + + $payload = array_merge($extra, $payload); + } + + return $payload; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php new file mode 100644 index 00000000..a2715658 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/ResponseTypeInterface.php @@ -0,0 +1,13 @@ +storage = $storage; + } + + /** + * Check if everything in required scope is contained in available scope. + * + * @param string $required_scope - A space-separated string of scopes. + * @param string $available_scope - A space-separated string of scopes. + * @return bool - 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 string $scope - A space-separated string of scopes. + * @return bool - 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); + } + } + + /** + * @param RequestInterface $request + * @return string + */ + public function getScopeFromRequest(RequestInterface $request) + { + // "scope" is valid if passed in either POST or QUERY + return $request->request('scope', $request->query('scope')); + } + + /** + * @param null $client_id + * @return mixed + */ + 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 array - An array of reserved scopes. + */ + public function getReservedScopes() + { + return array('openid', 'offline_access'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php new file mode 100644 index 00000000..f65cfa7b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ScopeInterface.php @@ -0,0 +1,35 @@ + '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', + ); + + /** + * @var array + */ + 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 "token" for + * Access Token and Authorization Code response types + * @param TokenTypeInterface $tokenType The token type object to use. Valid token types are "bearer" and "mac" + * @param ScopeInterface $scopeUtil The scope utility class to use to validate scope + * @param 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, + 'jwt_extra_payload_callable' => null, + '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(); + } + } + + /** + * @return AuthorizeControllerInterface + */ + public function getAuthorizeController() + { + if (is_null($this->authorizeController)) { + $this->authorizeController = $this->createDefaultAuthorizeController(); + } + + return $this->authorizeController; + } + + /** + * @return TokenController + */ + public function getTokenController() + { + if (is_null($this->tokenController)) { + $this->tokenController = $this->createDefaultTokenController(); + } + + return $this->tokenController; + } + + /** + * @return ResourceControllerInterface + */ + public function getResourceController() + { + if (is_null($this->resourceController)) { + $this->resourceController = $this->createDefaultResourceController(); + } + + return $this->resourceController; + } + + /** + * @return UserInfoControllerInterface + */ + public function getUserInfoController() + { + if (is_null($this->userInfoController)) { + $this->userInfoController = $this->createDefaultUserInfoController(); + } + + return $this->userInfoController; + } + + /** + * @param AuthorizeControllerInterface $authorizeController + */ + public function setAuthorizeController(AuthorizeControllerInterface $authorizeController) + { + $this->authorizeController = $authorizeController; + } + + /** + * @param TokenControllerInterface $tokenController + */ + public function setTokenController(TokenControllerInterface $tokenController) + { + $this->tokenController = $tokenController; + } + + /** + * @param ResourceControllerInterface $resourceController + */ + public function setResourceController(ResourceControllerInterface $resourceController) + { + $this->resourceController = $resourceController; + } + + /** + * @param UserInfoControllerInterface $userInfoController + */ + 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 RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object containing error messages (failure) or user claims (success) + * @return ResponseInterface + * + * @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 RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object containing error messages (failure) or access token (success) + * @return ResponseInterface + * + * @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; + } + + /** + * @param RequestInterface $request - Request object to grant access token + * @param ResponseInterface $response - Response object + * @return mixed + */ + 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 RequestInterface $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 ResponseInterface $response - Response object + * @param bool $is_authorized - TRUE or FALSE depending on whether the user authorized the access. + * @param mixed $user_id - Identifier of user who authorized the client + * @return ResponseInterface + * + * @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 + * + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return bool + * + * 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; + } + + /** + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @param string $scope - Scope + * @return mixed + */ + 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; + } + + /** + * @param RequestInterface $request - Request object + * @param ResponseInterface $response - Response object + * @return mixed + */ + 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; + } + + /** + * @param GrantTypeInterface $grantType + * @param mixed $identifier + */ + 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 object $storage - An object implementing one of the Storage interfaces + * @param mixed $key - If null, the storage is set to the key of each storage interface it implements + * + * @throws InvalidArgumentException + * @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 ClientCredentialsInterface) { + $this->storages['client_credentials'] = $storage; + } + } elseif ($key === 'client_credentials' && !isset($this->storages['client'])) { + if ($storage instanceof 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))); + } + } + } + + /** + * @param ResponseTypeInterface $responseType + * @param mixed $key + * + * @throws InvalidArgumentException + */ + 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))); + } + } + } + + /** + * @return ScopeInterface + */ + public function getScopeUtil() + { + if (!$this->scopeUtil) { + $storage = isset($this->storages['scope']) ? $this->storages['scope'] : null; + $this->scopeUtil = new Scope($storage); + } + + return $this->scopeUtil; + } + + /** + * @param ScopeInterface $scopeUtil + */ + public function setScopeUtil($scopeUtil) + { + $this->scopeUtil = $scopeUtil; + } + + /** + * @return AuthorizeControllerInterface + * @throws LogicException + */ + 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()); + } + + /** + * @return TokenControllerInterface + * @throws LogicException + */ + 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()); + } + + /** + * @return ResourceControllerInterface + * @throws LogicException + */ + 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()); + } + + /** + * @return UserInfoControllerInterface + * @throws LogicException + */ + 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()); + } + + /** + * @return Bearer + */ + protected function getDefaultTokenType() + { + $config = array_intersect_key($this->config, array_flip(explode(' ', 'token_param_name token_bearer_header_name'))); + + return new Bearer($config); + } + + /** + * @return array + * @throws LogicException + */ + 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; + } + + /** + * @return array + * @throws LogicException + */ + 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; + } + + /** + * @return AccessToken + */ + 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(); + } + + /** + * @return IdToken + */ + protected function getIdTokenResponseType() + { + if (isset($this->responseTypes['id_token'])) { + return $this->responseTypes['id_token']; + } + + return $this->createDefaultIdTokenResponseType(); + } + + /** + * @return IdTokenToken + */ + protected function getIdTokenTokenResponseType() + { + if (isset($this->responseTypes['id_token token'])) { + return $this->responseTypes['id_token token']; + } + + return $this->createDefaultIdTokenTokenResponseType(); + } + + /** + * For Resource Controller + * + * @return JwtAccessTokenStorage + * @throws LogicException + */ + 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 + * + * @return JwtAccessToken + * @throws LogicException + */ + 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 jwt_extra_payload_callable'))); + + return new JwtAccessToken($this->storages['public_key'], $tokenStorage, $refreshStorage, $config); + } + + /** + * @return AccessToken + * @throws LogicException + */ + 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); + } + + /** + * @return IdToken + * @throws LogicException + */ + 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); + } + + /** + * @return IdTokenToken + */ + protected function createDefaultIdTokenTokenResponseType() + { + return new IdTokenToken($this->getAccessTokenResponseType(), $this->getIdTokenResponseType()); + } + + /** + * @throws InvalidArgumentException + */ + 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.'); + } + } + + /** + * @param string $name + * @return string + */ + 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; + } + + /** + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * @return array + */ + public function getStorages() + { + return $this->storages; + } + + /** + * @param string $name + * @return object|null + */ + public function getStorage($name) + { + return isset($this->storages[$name]) ? $this->storages[$name] : null; + } + + /** + * @return array + */ + public function getGrantTypes() + { + return $this->grantTypes; + } + + /** + * @param string $name + * @return object|null + */ + public function getGrantType($name) + { + return isset($this->grantTypes[$name]) ? $this->grantTypes[$name] : null; + } + + /** + * @return array + */ + public function getResponseTypes() + { + return $this->responseTypes; + } + + /** + * @param string $name + * @return object|null + */ + 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; + } + + /** + * @return TokenTypeInterface + */ + public function getTokenType() + { + return $this->tokenType; + } + + /** + * @return ClientAssertionTypeInterface + */ + public function getClientAssertionType() + { + return $this->clientAssertionType; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setConfig($name, $value) + { + $this->config[$name] = $value; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getConfig($name, $default = null) + { + return isset($this->config[$name]) ? $this->config[$name] : $default; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php new file mode 100644 index 00000000..22428f2c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AccessTokenInterface.php @@ -0,0 +1,65 @@ + + */ +interface AccessTokenInterface +{ + /** + * Look up the supplied oauth_token from storage. + * + * We need to retrieve access token data as we create and verify tokens. + * + * @param string $oauth_token - oauth_token to be check with. + * + * @return array|null - An associative array as below, and return NULL if the supplied oauth_token is invalid: + * @code + * array( + * 'expires' => $expires, // Stored expiration in unix timestamp. + * 'client_id' => $client_id, // (optional) Stored client identifier. + * 'user_id' => $user_id, // (optional) Stored user identifier. + * 'scope' => $scope, // (optional) Stored scope values in space-separated string. + * 'id_token' => $id_token // (optional) Stored id_token (if "use_openid_connect" is true). + * ); + * @endcode + * + * @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 string $oauth_token - oauth_token to be stored. + * @param mixed $client_id - client identifier to be stored. + * @param mixed $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. + * + * @return BOOL true if an access token was unset, false if not + * @ingroup oauth2_section_6 + * + * @todo v2.0 include this method in interface. Omitted to maintain BC in v1.x + */ + //public function unsetAccessToken($access_token); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php new file mode 100644 index 00000000..2dbc138a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/AuthorizationCodeInterface.php @@ -0,0 +1,86 @@ + + */ +interface AuthorizationCodeInterface +{ + /** + * The Authorization Code grant type supports a response type of "code". + * + * @var string + * @see http://tools.ietf.org/html/rfc6749#section-1.4.1 + * @see http://tools.ietf.org/html/rfc6749#section-4.2 + */ + const RESPONSE_TYPE_CODE = "code"; + + /** + * Fetch authorization code data (probably the most common grant type). + * + * Retrieve the stored data for the given authorization code. + * + * Required for OAuth2::GRANT_TYPE_AUTH_CODE. + * + * @param $code + * Authorization code to be check with. + * + * @return + * An associative array as below, and NULL if the code is invalid + * @code + * return array( + * "client_id" => CLIENT_ID, // REQUIRED Stored client identifier + * "user_id" => USER_ID, // REQUIRED Stored user identifier + * "expires" => EXPIRES, // REQUIRED Stored expiration in unix timestamp + * "redirect_uri" => REDIRECT_URI, // REQUIRED Stored redirect URI + * "scope" => SCOPE, // OPTIONAL Stored scope values in space-separated string + * ); + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-4.1 + * + * @ingroup oauth2_section_4 + */ + public function getAuthorizationCode($code); + + /** + * 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 string $code - Authorization code to be stored. + * @param mixed $client_id - Client identifier to be stored. + * @param mixed $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. + * + * @ingroup oauth2_section_4 + */ + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null); + + /** + * once an Authorization Code is used, it must be expired + * + * @see http://tools.ietf.org/html/rfc6749#section-4.1.2 + * + * The client MUST NOT use the authorization code + * more than once. If an authorization code is used more than + * once, the authorization server MUST deny the request and SHOULD + * revoke (when possible) all tokens previously issued based on + * that authorization code + * + */ + public function expireAuthorizationCode($code); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php new file mode 100644 index 00000000..e60e9d3a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php @@ -0,0 +1,660 @@ + + * composer require thobbs/phpcassa:dev-master + * + * + * Once this is done, instantiate the connection: + * + * $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_server', array('127.0.0.1:9160')); + * + * + * Then, register the storage client: + * + * $storage = new OAuth2\Storage\Cassandra($cassandra); + * $storage->setClientDetails($client_id, $client_secret, $redirect_uri); + * + * + * @see test/lib/OAuth2/Storage/Bootstrap::getCassandraStorage + */ +class Cassandra implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + + private $cache; + + /** + * @var ConnectionPool + */ + protected $cassandra; + + /** + * @var array + */ + protected $config; + + /** + * Cassandra Storage! uses phpCassa + * + * @param ConnectionPool|array $connection + * @param array $config + * + * @throws InvalidArgumentException + */ + public function __construct($connection = array(), array $config = array()) + { + if ($connection instanceof ConnectionPool) { + $this->cassandra = $connection; + } else { + if (!is_array($connection)) { + throw new InvalidArgumentException('First argument to OAuth2\Storage\Cassandra must be an instance of phpcassa\Connection\ConnectionPool or a configuration array'); + } + $connection = array_merge(array( + 'keyspace' => 'oauth2', + 'servers' => null, + ), $connection); + + $this->cassandra = new ConnectionPool($connection['keyspace'], $connection['servers']); + } + + $this->config = array_merge(array( + // cassandra config + 'column_family' => 'auth', + + // key names + 'client_key' => 'oauth_clients:', + 'access_token_key' => 'oauth_access_tokens:', + 'refresh_token_key' => 'oauth_refresh_tokens:', + 'code_key' => 'oauth_authorization_codes:', + 'user_key' => 'oauth_users:', + 'jwt_key' => 'oauth_jwt:', + 'scope_key' => 'oauth_scopes:', + 'public_key_key' => 'oauth_public_keys:', + ), $config); + } + + /** + * @param $key + * @return bool|mixed + */ + protected function getValue($key) + { + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + try { + $value = $cf->get($key, new ColumnSlice("", "")); + $value = array_shift($value); + } catch (\cassandra\NotFoundException $e) { + return false; + } + + return json_decode($value, true); + } + + /** + * @param $key + * @param $value + * @param int $expire + * @return bool + */ + protected function setValue($key, $value, $expire = 0) + { + $this->cache[$key] = $value; + + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + $str = json_encode($value); + if ($expire > 0) { + try { + $seconds = $expire - time(); + // __data key set as C* requires a field, note: max TTL can only be 630720000 seconds + $cf->insert($key, array('__data' => $str), null, $seconds); + } catch (\Exception $e) { + return false; + } + } else { + try { + // __data key set as C* requires a field + $cf->insert($key, array('__data' => $str)); + } catch (\Exception $e) { + return false; + } + } + + return true; + } + + /** + * @param $key + * @return bool + */ + protected function expireValue($key) + { + unset($this->cache[$key]); + + $cf = new ColumnFamily($this->cassandra, $this->config['column_family']); + + if ($cf->get_count($key) > 0) { + try { + // __data key set as C* requires a field + $cf->remove($key, array('__data')); + } catch (\Exception $e) { + return false; + } + + return true; + } + + return false; + } + + /** + * @param string $code + * @return bool|mixed + */ + public function getAuthorizationCode($code) + { + return $this->getValue($this->config['code_key'] . $code); + } + + /** + * @param string $authorization_code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param int $expires + * @param string $scope + * @param string $id_token + * @return bool + */ + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + return $this->setValue( + $this->config['code_key'] . $authorization_code, + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + $expires + ); + } + + /** + * @param string $code + * @return bool + */ + public function expireAuthorizationCode($code) + { + $key = $this->config['code_key'] . $code; + unset($this->cache[$key]); + + return $this->expireValue($key); + } + + /** + * @param string $username + * @param string $password + * @return bool + */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param array $user + * @param string $password + * @return bool + */ + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + /** + * @param string $username + * @return array|bool|false + */ + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /** + * @param string $username + * @return array|bool + */ + public function getUser($username) + { + if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username, + ), $userInfo); + } + + /** + * @param string $username + * @param string $password + * @param string $first_name + * @param string $last_name + * @return bool + */ + public function setUser($username, $password, $first_name = null, $last_name = null) + { + $password = $this->hashPassword($password); + + return $this->setValue( + $this->config['user_key'] . $username, + compact('username', 'password', 'first_name', 'last_name') + ); + } + + /** + * @param mixed $client_id + * @param string $client_secret + * @return bool + */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return isset($client['client_secret']) + && $client['client_secret'] == $client_secret; + } + + /** + * @param $client_id + * @return bool + */ + public function isPublicClient($client_id) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return empty($client['client_secret']); + } + + /** + * @param $client_id + * @return array|bool|mixed + */ + public function getClientDetails($client_id) + { + return $this->getValue($this->config['client_key'] . $client_id); + } + + /** + * @param $client_id + * @param null $client_secret + * @param null $redirect_uri + * @param null $grant_types + * @param null $scope + * @param null $user_id + * @return bool + */ + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') + ); + } + + /** + * @param $client_id + * @param $grant_type + * @return bool + */ + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /** + * @param $refresh_token + * @return bool|mixed + */ + public function getRefreshToken($refresh_token) + { + return $this->getValue($this->config['refresh_token_key'] . $refresh_token); + } + + /** + * @param $refresh_token + * @param $client_id + * @param $user_id + * @param $expires + * @param null $scope + * @return bool + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['refresh_token_key'] . $refresh_token, + compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + /** + * @param $refresh_token + * @return bool + */ + public function unsetRefreshToken($refresh_token) + { + return $this->expireValue($this->config['refresh_token_key'] . $refresh_token); + } + + /** + * @param string $access_token + * @return array|bool|mixed|null + */ + public function getAccessToken($access_token) + { + return $this->getValue($this->config['access_token_key'].$access_token); + } + + /** + * @param string $access_token + * @param mixed $client_id + * @param mixed $user_id + * @param int $expires + * @param null $scope + * @return bool + */ + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['access_token_key'].$access_token, + compact('access_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + /** + * @param $access_token + * @return bool + */ + public function unsetAccessToken($access_token) + { + return $this->expireValue($this->config['access_token_key'] . $access_token); + } + + /** + * @param $scope + * @return bool + */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + + $result = $this->getValue($this->config['scope_key'].'supported:global'); + + $supportedScope = explode(' ', (string) $result); + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + /** + * @param null $client_id + * @return bool|mixed + */ + public function getDefaultScope($client_id = null) + { + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'default:global'); + } + + return $result; + } + + /** + * @param $scope + * @param null $client_id + * @param string $type + * @return bool + * @throws \InvalidArgumentException + */ + public function setScope($scope, $client_id = null, $type = 'supported') + { + if (!in_array($type, array('default', 'supported'))) { + throw new \InvalidArgumentException('"$type" must be one of "default", "supported"'); + } + + if (is_null($client_id)) { + $key = $this->config['scope_key'].$type.':global'; + } else { + $key = $this->config['scope_key'].$type.':'.$client_id; + } + + return $this->setValue($key, $scope); + } + + /** + * @param $client_id + * @param $subject + * @return bool|null + */ + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject ) { + return $jwt['key']; + } + + return null; + } + + /** + * @param $client_id + * @param $key + * @param null $subject + * @return bool + */ + public function setClientKey($client_id, $key, $subject = null) + { + return $this->setValue($this->config['jwt_key'] . $client_id, array( + 'key' => $key, + 'subject' => $subject + )); + } + + /** + * @param $client_id + * @return bool|null + */ + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + /** + * @param $client_id + * @param $subject + * @param $audience + * @param $expiration + * @param $jti + * @throws \Exception + */ + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs cassandra implementation. + throw new \Exception('getJti() for the Cassandra driver is currently unimplemented.'); + } + + /** + * @param $client_id + * @param $subject + * @param $audience + * @param $expiration + * @param $jti + * @throws \Exception + */ + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs cassandra implementation. + throw new \Exception('setJti() for the Cassandra driver is currently unimplemented.'); + } + + /** + * @param string $client_id + * @return mixed + */ + public function getPublicKey($client_id = '') + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['public_key']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['public_key']; + } + } + + /** + * @param string $client_id + * @return mixed + */ + public function getPrivateKey($client_id = '') + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['private_key']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['private_key']; + } + } + + /** + * @param null $client_id + * @return mixed|string + */ + public function getEncryptionAlgorithm($client_id = null) + { + $public_key = $this->getValue($this->config['public_key_key'] . $client_id); + if (is_array($public_key)) { + return $public_key['encryption_algorithm']; + } + $public_key = $this->getValue($this->config['public_key_key']); + if (is_array($public_key)) { + return $public_key['encryption_algorithm']; + } + + return 'RS256'; + } + + /** + * @param mixed $user_id + * @param string $claims + * @return array|bool + */ + public function getUserClaims($user_id, $claims) + { + $userDetails = $this->getUserDetails($user_id); + if (!is_array($userDetails)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + /** + * @param $claim + * @param $userDetails + * @return array + */ + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + if ($value == 'email_verified') { + $userClaims[$value] = $userDetails[$value]=='true' ? true : false; + } else { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + } + + return $userClaims; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php new file mode 100644 index 00000000..3318c696 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientCredentialsInterface.php @@ -0,0 +1,49 @@ + + */ +interface ClientCredentialsInterface extends ClientInterface +{ + + /** + * Make sure that the client credentials is valid. + * + * @param $client_id + * Client identifier to be check with. + * @param $client_secret + * (optional) If a secret is required, check that they've given the right one. + * + * @return + * TRUE if the client credentials are valid, and MUST return FALSE if it isn't. + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-3.1 + * + * @ingroup oauth2_section_3 + */ + public function checkClientCredentials($client_id, $client_secret = null); + + /** + * Determine if the client is a "public" client, and therefore + * does not require passing credentials for certain grant types + * + * @param $client_id + * Client identifier to be check with. + * + * @return + * TRUE if the client is public, and FALSE if it isn't. + * @endcode + * + * @see http://tools.ietf.org/html/rfc6749#section-2.3 + * @see https://github.com/bshaffer/oauth2-server-php/issues/257 + * + * @ingroup oauth2_section_2 + */ + public function isPublicClient($client_id); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php new file mode 100644 index 00000000..09a5bffc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ClientInterface.php @@ -0,0 +1,66 @@ + + */ +interface ClientInterface +{ + /** + * Get client details corresponding client_id. + * + * OAuth says we should store request URIs for each registered client. + * Implement this function to grab the stored URI for a given client id. + * + * @param $client_id + * Client identifier to be check with. + * + * @return array + * Client details. The only mandatory key in the array is "redirect_uri". + * This function MUST return FALSE if the given client does not exist or is + * invalid. "redirect_uri" can be space-delimited to allow for multiple valid uris. + * + * return array( + * "redirect_uri" => REDIRECT_URI, // REQUIRED redirect_uri registered for the client + * "client_id" => CLIENT_ID, // OPTIONAL the client id + * "grant_types" => GRANT_TYPES, // OPTIONAL an array of restricted grant types + * "user_id" => USER_ID, // OPTIONAL the user identifier associated with this client + * "scope" => SCOPE, // OPTIONAL the scopes allowed for this client + * ); + * + * + * @ingroup oauth2_section_4 + */ + public function getClientDetails($client_id); + + /** + * Get the scope associated with this client + * + * @return + * STRING the space-delineated scope list for the specified client_id + */ + public function getClientScope($client_id); + + /** + * Check restricted grant types of corresponding client identifier. + * + * If you want to restrict clients to certain grant types, override this + * function. + * + * @param $client_id + * Client identifier to be check with. + * @param $grant_type + * Grant type to be check with + * + * @return + * TRUE if the grant type is supported by this client identifier, and + * FALSE if it isn't. + * + * @ingroup oauth2_section_4 + */ + public function checkRestrictedGrantType($client_id, $grant_type); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php new file mode 100755 index 00000000..9e8148b6 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php @@ -0,0 +1,331 @@ + + */ +class CouchbaseDB implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof \Couchbase) { + $this->db = $connection; + } else { + if (!is_array($connection) || !is_array($connection['servers'])) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\CouchbaseDB must be an instance of Couchbase or a configuration array containing a server array'); + } + + $this->db = new \Couchbase($connection['servers'], (!isset($connection['username'])) ? '' : $connection['username'], (!isset($connection['password'])) ? '' : $connection['password'], $connection['bucket'], false); + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + ), $config); + } + + // Helper function to access couchbase item by type: + protected function getObjectByType($name,$id) + { + return json_decode($this->db->get($this->config[$name].'-'.$id),true); + } + + // Helper function to set couchbase item by type: + protected function setObjectByType($name,$id,$array) + { + $array['type'] = $name; + + return $this->db->set($this->config[$name].'-'.$id,json_encode($array)); + } + + // Helper function to delete couchbase item by type, wait for persist to at least 1 node + protected function deleteObjectByType($name,$id) + { + $this->db->delete($this->config[$name].'-'.$id,"",1); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->getObjectByType('client_table',$client_id)) { + return $result['client_secret'] == $client_secret; + } + + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->getObjectByType('client_table',$client_id)) { + return false; + } + + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->getObjectByType('client_table',$client_id); + + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + + $this->setObjectByType('client_table',$client_id, array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )); + } else { + $this->setObjectByType('client_table',$client_id, array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )); + } + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->getObjectByType('access_token_table',$access_token); + + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $this->setObjectByType('access_token_table',$access_token, array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )); + } else { + $this->setObjectByType('access_token_table',$access_token, array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )); + } + + return true; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->getObjectByType('code_table',$code); + + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $this->setObjectByType('code_table',$code, array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )); + } else { + $this->setObjectByType('code_table',$code,array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )); + } + + return true; + } + + public function expireAuthorizationCode($code) + { + $this->deleteObjectByType('code_table',$code); + + return true; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->getObjectByType('refresh_token_table',$refresh_token); + + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $this->setObjectByType('refresh_token_table',$refresh_token, array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + )); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $this->deleteObjectByType('refresh_token_table',$refresh_token); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->getObjectByType('user_table',$username); + + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $this->setObjectByType('user_table',$username, array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )); + + } else { + $this->setObjectByType('user_table',$username, array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )); + + } + + return true; + } + + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getObjectByType('jwt_table',$client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject) { + return $jwt['key']; + } + + return false; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs couchbase implementation. + throw new \Exception('getJti() for the Couchbase driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs couchbase implementation. + throw new \Exception('setJti() for the Couchbase driver is currently unimplemented.'); + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php new file mode 100644 index 00000000..a54cb371 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php @@ -0,0 +1,540 @@ + + * composer require aws/aws-sdk-php:dev-master + * + * + * Once this is done, instantiate the DynamoDB client + * + * $storage = new OAuth2\Storage\Dynamodb(array("key" => "YOURKEY", "secret" => "YOURSECRET", "region" => "YOURREGION")); + * + * + * Table : + * - oauth_access_tokens (primary hash key : access_token) + * - oauth_authorization_codes (primary hash key : authorization_code) + * - oauth_clients (primary hash key : client_id) + * - oauth_jwt (primary hash key : client_id, primary range key : subject) + * - oauth_public_keys (primary hash key : client_id) + * - oauth_refresh_tokens (primary hash key : refresh_token) + * - oauth_scopes (primary hash key : scope, secondary index : is_default-index hash key is_default) + * - oauth_users (primary hash key : username) + * + * @author Frederic AUGUSTE + */ +class DynamoDB implements + AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + protected $client; + protected $config; + + public function __construct($connection, $config = array()) + { + if (!($connection instanceof DynamoDbClient)) { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + if (!array_key_exists("key",$connection) || !array_key_exists("secret",$connection) || !array_key_exists("region",$connection) ) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Dynamodb must be an instance a configuration array containt key, secret, region'); + } + $this->client = DynamoDbClient::factory(array( + 'key' => $connection["key"], + 'secret' => $connection["secret"], + 'region' =>$connection["region"] + )); + } else { + $this->client = $connection; + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', + ), $config); + } + + /* OAuth2\Storage\ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + return $result->count()==1 && $result["Item"]["client_secret"]["S"] == $client_secret; + } + + public function isPublicClient($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + + if ($result->count()==0) { + return false ; + } + + return empty($result["Item"]["client_secret"]); + } + + /* OAuth2\Storage\ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['client_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $result = $this->dynamo2array($result); + foreach (array('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') as $key => $val) { + if (!array_key_exists ($val, $result)) { + $result[$val] = null; + } + } + + return $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + $clientData = compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['client_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* OAuth2\Storage\AccessTokenInterface */ + public function getAccessToken($access_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['access_token_table'], + "Key" => array('access_token' => array('S' => $access_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (array_key_exists ('expires', $token)) { + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('access_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['access_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + public function unsetAccessToken($access_token) + { + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['access_token_table'], + 'Key' => $this->client->formatAttributes(array("access_token" => $access_token)), + 'ReturnValues' => 'ALL_OLD', + )); + + return null !== $result->get('Attributes'); + } + + /* OAuth2\Storage\AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['code_table'], + "Key" => array('authorization_code' => array('S' => $code)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + if (!array_key_exists("id_token", $token )) { + $token['id_token'] = null; + } + $token['expires'] = strtotime($token['expires']); + + return $token; + + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'id_token', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['code_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function expireAuthorizationCode($code) + { + + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['code_table'], + 'Key' => $this->client->formatAttributes(array("authorization_code" => $code)) + )); + + return true; + } + + /* OAuth2\Storage\UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /* UserClaimsInterface */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + if ($value == 'email_verified') { + $userClaims[$value] = $userDetails[$value]=='true' ? true : false; + } else { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + } + + return $userClaims; + } + + /* OAuth2\Storage\RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['refresh_token_table'], + "Key" => array('refresh_token' => array('S' => $refresh_token)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['expires'] = strtotime($token['expires']); + + return $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $clientData = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->client->deleteItem(array( + 'TableName' => $this->config['refresh_token_table'], + 'Key' => $this->client->formatAttributes(array("refresh_token" => $refresh_token)) + )); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + public function getUser($username) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['user_table'], + "Key" => array('username' => array('S' => $username)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + $token['user_id'] = $username; + + return $token; + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + // do not store in plaintext + $password = $this->hashPassword($password); + + $clientData = compact('username', 'password', 'first_name', 'last_name'); + $clientData = array_filter($clientData, 'self::isNotEmpty'); + + $result = $this->client->putItem(array( + 'TableName' => $this->config['user_table'], + 'Item' => $this->client->formatAttributes($clientData) + )); + + return true; + + } + + /* ScopeInterface */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + $scope_query = array(); + $count = 0; + foreach ($scope as $key => $val) { + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'Select' => 'COUNT', + 'KeyConditions' => array( + 'scope' => array( + 'AttributeValueList' => array(array('S' => $val)), + 'ComparisonOperator' => 'EQ' + ) + ) + )); + $count += $result['Count']; + } + + return $count == count($scope); + } + + public function getDefaultScope($client_id = null) + { + + $result = $this->client->query(array( + 'TableName' => $this->config['scope_table'], + 'IndexName' => 'is_default-index', + 'Select' => 'ALL_ATTRIBUTES', + 'KeyConditions' => array( + 'is_default' => array( + 'AttributeValueList' => array(array('S' => 'true')), + 'ComparisonOperator' => 'EQ', + ), + ) + )); + $defaultScope = array(); + if ($result->count() > 0) { + $array = $result->toArray(); + foreach ($array["Items"] as $item) { + $defaultScope[] = $item['scope']['S']; + } + + return empty($defaultScope) ? null : implode(' ', $defaultScope); + } + + return null; + } + + /* JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['jwt_table'], + "Key" => array('client_id' => array('S' => $client_id), 'subject' => array('S' => $subject)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO not use. + } + + /* PublicKeyInterface */ + public function getPublicKey($client_id = '0') + { + + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['public_key']; + + } + + public function getPrivateKey($client_id = '0') + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return false ; + } + $token = $this->dynamo2array($result); + + return $token['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + $result = $this->client->getItem(array( + "TableName"=> $this->config['public_key_table'], + "Key" => array('client_id' => array('S' => $client_id)) + )); + if ($result->count()==0) { + return 'RS256' ; + } + $token = $this->dynamo2array($result); + + return $token['encryption_algorithm']; + } + + /** + * Transform dynamodb resultset to an array. + * @param $dynamodbResult + * @return $array + */ + private function dynamo2array($dynamodbResult) + { + $result = array(); + foreach ($dynamodbResult["Item"] as $key => $val) { + $result[$key] = $val["S"]; + $result[] = $val["S"]; + } + + return $result; + } + + private static function isNotEmpty($value) + { + return null !== $value && '' !== $value; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php new file mode 100644 index 00000000..6ccacd6d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php @@ -0,0 +1,87 @@ + + */ +class JwtAccessToken implements JwtAccessTokenInterface +{ + protected $publicKeyStorage; + protected $tokenStorage; + protected $encryptionUtil; + + /** + * @param OAuth2\Encryption\PublicKeyInterface $publicKeyStorage the public key encryption to use + * @param OAuth2\Storage\AccessTokenInterface $tokenStorage OPTIONAL persist the access token to another storage. This is useful if + * you want to retain access token grant information somewhere, but + * is not necessary when using this grant type. + * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions. + */ + public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null) + { + $this->publicKeyStorage = $publicKeyStorage; + $this->tokenStorage = $tokenStorage; + if (is_null($encryptionUtil)) { + $encryptionUtil = new Jwt; + } + $this->encryptionUtil = $encryptionUtil; + } + + public function getAccessToken($oauth_token) + { + // just decode the token, don't verify + if (!$tokenData = $this->encryptionUtil->decode($oauth_token, null, false)) { + return false; + } + + $client_id = isset($tokenData['aud']) ? $tokenData['aud'] : null; + $public_key = $this->publicKeyStorage->getPublicKey($client_id); + $algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id); + + // now that we have the client_id, verify the token + if (false === $this->encryptionUtil->decode($oauth_token, $public_key, array($algorithm))) { + return false; + } + + // normalize the JWT claims to the format expected by other components in this library + return $this->convertJwtToOAuth2($tokenData); + } + + public function setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope = null) + { + if ($this->tokenStorage) { + return $this->tokenStorage->setAccessToken($oauth_token, $client_id, $user_id, $expires, $scope); + } + } + + public function unsetAccessToken($access_token) + { + if ($this->tokenStorage) { + return $this->tokenStorage->unsetAccessToken($access_token); + } + } + + + // converts a JWT access token into an OAuth2-friendly format + protected function convertJwtToOAuth2($tokenData) + { + $keyMapping = array( + 'aud' => 'client_id', + 'exp' => 'expires', + 'sub' => 'user_id' + ); + + foreach ($keyMapping as $jwtKey => $oauth2Key) { + if (isset($tokenData[$jwtKey])) { + $tokenData[$oauth2Key] = $tokenData[$jwtKey]; + unset($tokenData[$jwtKey]); + } + } + + return $tokenData; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php new file mode 100644 index 00000000..3abb2aa2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessTokenInterface.php @@ -0,0 +1,14 @@ + + */ +interface JwtAccessTokenInterface extends AccessTokenInterface +{ + +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php new file mode 100644 index 00000000..c83aa72e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtBearerInterface.php @@ -0,0 +1,74 @@ + + */ +interface JwtBearerInterface +{ + /** + * Get the public key associated with a client_id + * + * @param $client_id + * Client identifier to be checked with. + * + * @return + * STRING Return the public key for the client_id if it exists, and MUST return FALSE if it doesn't. + */ + public function getClientKey($client_id, $subject); + + /** + * Get a jti (JSON token identifier) by matching against the client_id, subject, audience and expiration. + * + * @param $client_id + * Client identifier to match. + * + * @param $subject + * The subject to match. + * + * @param $audience + * The audience to match. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to match. + * + * @return + * An associative array as below, and return NULL if the jti does not exist. + * - issuer: Stored client identifier. + * - subject: Stored subject. + * - audience: Stored audience. + * - expires: Stored expiration in unix timestamp. + * - jti: The stored jti. + */ + public function getJti($client_id, $subject, $audience, $expiration, $jti); + + /** + * Store a used jti so that we can check against it to prevent replay attacks. + * @param $client_id + * Client identifier to insert. + * + * @param $subject + * The subject to insert. + * + * @param $audience + * The audience to insert. + * + * @param $expiration + * The expiration of the jti. + * + * @param $jti + * The jti to insert. + */ + public function setJti($client_id, $subject, $audience, $expiration, $jti); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php new file mode 100644 index 00000000..2c60b71c --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php @@ -0,0 +1,381 @@ + + */ +class Memory implements AuthorizationCodeInterface, + UserCredentialsInterface, + UserClaimsInterface, + AccessTokenInterface, + ClientCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + public $authorizationCodes; + public $userCredentials; + public $clientCredentials; + public $refreshTokens; + public $accessTokens; + public $jwt; + public $jti; + public $supportedScopes; + public $defaultScope; + public $keys; + + public function __construct($params = array()) + { + $params = array_merge(array( + 'authorization_codes' => array(), + 'user_credentials' => array(), + 'client_credentials' => array(), + 'refresh_tokens' => array(), + 'access_tokens' => array(), + 'jwt' => array(), + 'jti' => array(), + 'default_scope' => null, + 'supported_scopes' => array(), + 'keys' => array(), + ), $params); + + $this->authorizationCodes = $params['authorization_codes']; + $this->userCredentials = $params['user_credentials']; + $this->clientCredentials = $params['client_credentials']; + $this->refreshTokens = $params['refresh_tokens']; + $this->accessTokens = $params['access_tokens']; + $this->jwt = $params['jwt']; + $this->jti = $params['jti']; + $this->supportedScopes = $params['supported_scopes']; + $this->defaultScope = $params['default_scope']; + $this->keys = $params['keys']; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + if (!isset($this->authorizationCodes[$code])) { + return false; + } + + return array_merge(array( + 'authorization_code' => $code, + ), $this->authorizationCodes[$code]); + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'); + + return true; + } + + public function setAuthorizationCodes($authorization_codes) + { + $this->authorizationCodes = $authorization_codes; + } + + public function expireAuthorizationCode($code) + { + unset($this->authorizationCodes[$code]); + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + $userDetails = $this->getUserDetails($username); + + return $userDetails && $userDetails['password'] && $userDetails['password'] === $password; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + $this->userCredentials[$username] = array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName, + ); + + return true; + } + + public function getUserDetails($username) + { + if (!isset($this->userCredentials[$username])) { + return false; + } + + return array_merge(array( + 'user_id' => $username, + 'password' => null, + 'first_name' => null, + 'last_name' => null, + ), $this->userCredentials[$username]); + } + + /* UserClaimsInterface */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + + return $userClaims; + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + return isset($this->clientCredentials[$client_id]['client_secret']) && $this->clientCredentials[$client_id]['client_secret'] === $client_secret; + } + + public function isPublicClient($client_id) + { + if (!isset($this->clientCredentials[$client_id])) { + return false; + } + + return empty($this->clientCredentials[$client_id]['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + if (!isset($this->clientCredentials[$client_id])) { + return false; + } + + $clientDetails = array_merge(array( + 'client_id' => $client_id, + 'client_secret' => null, + 'redirect_uri' => null, + 'scope' => null, + ), $this->clientCredentials[$client_id]); + + return $clientDetails; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + if (isset($this->clientCredentials[$client_id]['grant_types'])) { + $grant_types = explode(' ', $this->clientCredentials[$client_id]['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + $this->clientCredentials[$client_id] = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + + return true; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + return isset($this->refreshTokens[$refresh_token]) ? $this->refreshTokens[$refresh_token] : false; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $this->refreshTokens[$refresh_token] = compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + if (isset($this->refreshTokens[$refresh_token])) { + unset($this->refreshTokens[$refresh_token]); + + return true; + } + + return false; + } + + public function setRefreshTokens($refresh_tokens) + { + $this->refreshTokens = $refresh_tokens; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + return isset($this->accessTokens[$access_token]) ? $this->accessTokens[$access_token] : false; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null, $id_token = null) + { + $this->accessTokens[$access_token] = compact('access_token', 'client_id', 'user_id', 'expires', 'scope', 'id_token'); + + return true; + } + + public function unsetAccessToken($access_token) + { + if (isset($this->accessTokens[$access_token])) { + unset($this->accessTokens[$access_token]); + + return true; + } + + return false; + } + + public function scopeExists($scope) + { + $scope = explode(' ', trim($scope)); + + return (count(array_diff($scope, $this->supportedScopes)) == 0); + } + + public function getDefaultScope($client_id = null) + { + return $this->defaultScope; + } + + /*JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + if (isset($this->jwt[$client_id])) { + $jwt = $this->jwt[$client_id]; + if ($jwt) { + if ($jwt["subject"] == $subject) { + return $jwt["key"]; + } + } + } + + return false; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + foreach ($this->jti as $storedJti) { + if ($storedJti['issuer'] == $client_id && $storedJti['subject'] == $subject && $storedJti['audience'] == $audience && $storedJti['expires'] == $expires && $storedJti['jti'] == $jti) { + return array( + 'issuer' => $storedJti['issuer'], + 'subject' => $storedJti['subject'], + 'audience' => $storedJti['audience'], + 'expires' => $storedJti['expires'], + 'jti' => $storedJti['jti'] + ); + } + } + + return null; + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $this->jti[] = array('issuer' => $client_id, 'subject' => $subject, 'audience' => $audience, 'expires' => $expires, 'jti' => $jti); + } + + /*PublicKeyInterface */ + public function getPublicKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['public_key']; + } + + // use a global encryption pair + if (isset($this->keys['public_key'])) { + return $this->keys['public_key']; + } + + return false; + } + + public function getPrivateKey($client_id = null) + { + if (isset($this->keys[$client_id])) { + return $this->keys[$client_id]['private_key']; + } + + // use a global encryption pair + if (isset($this->keys['private_key'])) { + return $this->keys['private_key']; + } + + return false; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if (isset($this->keys[$client_id]['encryption_algorithm'])) { + return $this->keys[$client_id]['encryption_algorithm']; + } + + // use a global encryption algorithm + if (isset($this->keys['encryption_algorithm'])) { + return $this->keys['encryption_algorithm']; + } + + return 'RS256'; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php new file mode 100644 index 00000000..eea06e31 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php @@ -0,0 +1,392 @@ + + */ +class Mongo implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof \MongoDB) { + $this->db = $connection; + } else { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB or a configuration array'); + } + $server = sprintf('mongodb://%s:%d', $connection['host'], $connection['port']); + $m = new \MongoClient($server); + $this->db = $m->{$connection['database']}; + } + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'key_table' => 'oauth_keys', + 'jwt_table' => 'oauth_jwt', + ), $config); + } + + // Helper function to access a MongoDB collection by `type`: + protected function collection($name) + { + return $this->db->{$this->config[$name]}; + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return $result['client_secret'] == $client_secret; + } + + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return false; + } + + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->collection('client_table')->findOne(array('client_id' => $client_id)); + + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $this->collection('client_table')->update( + array('client_id' => $client_id), + array('$set' => array( + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )) + ); + } else { + $client = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + $this->collection('client_table')->insert($client); + } + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->collection('access_token_table')->findOne(array('access_token' => $access_token)); + + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $this->collection('access_token_table')->update( + array('access_token' => $access_token), + array('$set' => array( + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )) + ); + } else { + $token = array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ); + $this->collection('access_token_table')->insert($token); + } + + return true; + } + + public function unsetAccessToken($access_token) + { + $result = $this->collection('access_token_table')->remove(array( + 'access_token' => $access_token + ), array('w' => 1)); + + return $result['n'] > 0; + } + + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->collection('code_table')->findOne(array('authorization_code' => $code)); + + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $this->collection('code_table')->update( + array('authorization_code' => $code), + array('$set' => array( + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )) + ); + } else { + $token = array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ); + $this->collection('code_table')->insert($token); + } + + return true; + } + + public function expireAuthorizationCode($code) + { + $this->collection('code_table')->remove(array('authorization_code' => $code)); + + return true; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->collection('refresh_token_table')->findOne(array('refresh_token' => $refresh_token)); + + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $token = array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + ); + $this->collection('refresh_token_table')->insert($token); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->collection('refresh_token_table')->remove(array( + 'refresh_token' => $refresh_token + ), array('w' => 1)); + + return $result['n'] > 0; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->collection('user_table')->findOne(array('username' => $username)); + + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $this->collection('user_table')->update( + array('username' => $username), + array('$set' => array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )) + ); + } else { + $user = array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ); + $this->collection('user_table')->insert($user); + } + + return true; + } + + public function getClientKey($client_id, $subject) + { + $result = $this->collection('jwt_table')->findOne(array( + 'client_id' => $client_id, + 'subject' => $subject + )); + + return is_null($result) ? false : $result['key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } + + public function getPublicKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['public_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['public_key']; + } + + public function getPrivateKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['private_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['encryption_algorithm']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? 'RS256' : $result['encryption_algorithm']; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php new file mode 100644 index 00000000..64f740fc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php @@ -0,0 +1,380 @@ + + */ +class MongoDB implements AuthorizationCodeInterface, + UserCredentialsInterface, + AccessTokenInterface, + ClientCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + PublicKeyInterface, + OpenIDAuthorizationCodeInterface +{ + protected $db; + protected $config; + + public function __construct($connection, $config = array()) + { + if ($connection instanceof Database) { + $this->db = $connection; + } else { + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Mongo must be an instance of MongoDB\Database or a configuration array'); + } + $server = sprintf('mongodb://%s:%d', $connection['host'], $connection['port']); + $m = new Client($server); + $this->db = $m->selectDatabase($connection['database']); + } + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'jti_table' => 'oauth_jti', + 'scope_table' => 'oauth_scopes', + 'key_table' => 'oauth_keys', + ), $config); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return $result['client_secret'] == $client_secret; + } + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->collection('client_table')->findOne(array('client_id' => $client_id))) { + return false; + } + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->collection('client_table')->findOne(array('client_id' => $client_id)); + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $result = $this->collection('client_table')->updateOne( + array('client_id' => $client_id), + array('$set' => array( + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + )) + ); + return $result->getMatchedCount() > 0; + } + $client = array( + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ); + $result = $this->collection('client_table')->insertOne($client); + return $result->getInsertedCount() > 0; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + return in_array($grant_type, $grant_types); + } + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->collection('access_token_table')->findOne(array('access_token' => $access_token)); + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $result = $this->collection('access_token_table')->updateOne( + array('access_token' => $access_token), + array('$set' => array( + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + )) + ); + return $result->getMatchedCount() > 0; + } + $token = array( + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ); + $result = $this->collection('access_token_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function unsetAccessToken($access_token) + { + $result = $this->collection('access_token_table')->deleteOne(array( + 'access_token' => $access_token + )); + return $result->getDeletedCount() > 0; + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->collection('code_table')->findOne(array( + 'authorization_code' => $code + )); + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $result = $this->collection('code_table')->updateOne( + array('authorization_code' => $code), + array('$set' => array( + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + )) + ); + return $result->getMatchedCount() > 0; + } + $token = array( + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ); + $result = $this->collection('code_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function expireAuthorizationCode($code) + { + $result = $this->collection('code_table')->deleteOne(array( + 'authorization_code' => $code + )); + return $result->getDeletedCount() > 0; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->collection('refresh_token_table')->findOne(array( + 'refresh_token' => $refresh_token + )); + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $token = array( + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + ); + $result = $this->collection('refresh_token_table')->insertOne($token); + return $result->getInsertedCount() > 0; + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->collection('refresh_token_table')->deleteOne(array( + 'refresh_token' => $refresh_token + )); + return $result->getDeletedCount() > 0; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->collection('user_table')->findOne(array('username' => $username)); + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $result = $this->collection('user_table')->updateOne( + array('username' => $username), + array('$set' => array( + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + )) + ); + + return $result->getMatchedCount() > 0; + } + + $user = array( + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ); + $result = $this->collection('user_table')->insertOne($user); + return $result->getInsertedCount() > 0; + } + + public function getClientKey($client_id, $subject) + { + $result = $this->collection('jwt_table')->findOne(array( + 'client_id' => $client_id, + 'subject' => $subject + )); + return is_null($result) ? false : $result['key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + return null; + } + + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } + + public function getPublicKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['public_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['public_key']; + } + + public function getPrivateKey($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['private_key']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? false : $result['private_key']; + } + + public function getEncryptionAlgorithm($client_id = null) + { + if ($client_id) { + $result = $this->collection('key_table')->findOne(array( + 'client_id' => $client_id + )); + if ($result) { + return $result['encryption_algorithm']; + } + } + + $result = $this->collection('key_table')->findOne(array( + 'client_id' => null + )); + return is_null($result) ? 'RS256' : $result['encryption_algorithm']; + } + + // Helper function to access a MongoDB collection by `type`: + protected function collection($name) + { + return $this->db->{$this->config[$name]}; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php new file mode 100644 index 00000000..074cee44 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php @@ -0,0 +1,731 @@ + + */ +class Pdo implements + AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + PublicKeyInterface, + UserClaimsInterface, + OpenIDAuthorizationCodeInterface +{ + /** + * @var \PDO + */ + protected $db; + + /** + * @var array + */ + protected $config; + + /** + * @param mixed $connection + * @param array $config + * + * @throws InvalidArgumentException + */ + public function __construct($connection, $config = array()) + { + if (!$connection instanceof \PDO) { + if (is_string($connection)) { + $connection = array('dsn' => $connection); + } + if (!is_array($connection)) { + throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array'); + } + if (!isset($connection['dsn'])) { + throw new \InvalidArgumentException('configuration array must contain "dsn"'); + } + // merge optional parameters + $connection = array_merge(array( + 'username' => null, + 'password' => null, + 'options' => array(), + ), $connection); + $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']); + } + $this->db = $connection; + + // debugging + $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $this->config = array_merge(array( + 'client_table' => 'oauth_clients', + 'access_token_table' => 'oauth_access_tokens', + 'refresh_token_table' => 'oauth_refresh_tokens', + 'code_table' => 'oauth_authorization_codes', + 'user_table' => 'oauth_users', + 'jwt_table' => 'oauth_jwt', + 'jti_table' => 'oauth_jti', + 'scope_table' => 'oauth_scopes', + 'public_key_table' => 'oauth_public_keys', + ), $config); + } + + /** + * @param string $client_id + * @param null|string $client_secret + * @return bool + */ + public function checkClientCredentials($client_id, $client_secret = null) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + // make this extensible + return $result && $result['client_secret'] == $client_secret; + } + + /** + * @param string $client_id + * @return bool + */ + public function isPublicClient($client_id) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + + if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return false; + } + + return empty($result['client_secret']); + } + + /** + * @param string $client_id + * @return array|mixed + */ + public function getClientDetails($client_id) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); + $stmt->execute(compact('client_id')); + + return $stmt->fetch(\PDO::FETCH_ASSOC); + } + + /** + * @param string $client_id + * @param null|string $client_secret + * @param null|string $redirect_uri + * @param null|array $grant_types + * @param null|string $scope + * @param null|string $user_id + * @return bool + */ + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + // if it exists, update it. + if ($this->getClientDetails($client_id)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, scope=:scope, user_id=:user_id where client_id=:client_id', $this->config['client_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :scope, :user_id)', $this->config['client_table'])); + } + + return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id')); + } + + /** + * @param $client_id + * @param $grant_type + * @return bool + */ + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /** + * @param string $access_token + * @return array|bool|mixed|null + */ + public function getAccessToken($access_token) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table'])); + + $token = $stmt->execute(compact('access_token')); + if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert date string back to timestamp + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + /** + * @param string $access_token + * @param mixed $client_id + * @param mixed $user_id + * @param int $expires + * @param string $scope + * @return bool + */ + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $stmt = $this->db->prepare(sprintf('UPDATE %s SET client_id=:client_id, expires=:expires, user_id=:user_id, scope=:scope where access_token=:access_token', $this->config['access_token_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table'])); + } + + return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope')); + } + + /** + * @param $access_token + * @return bool + */ + public function unsetAccessToken($access_token) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table'])); + + $stmt->execute(compact('access_token')); + + return $stmt->rowCount() > 0; + } + + /* OAuth2\Storage\AuthorizationCodeInterface */ + /** + * @param string $code + * @return mixed + */ + public function getAuthorizationCode($code) + { + $stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table'])); + $stmt->execute(compact('code')); + + if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert date string back to timestamp + $code['expires'] = strtotime($code['expires']); + } + + return $code; + } + + /** + * @param string $code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param int $expires + * @param string $scope + * @param string $id_token + * @return bool|mixed + */ + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + if (func_num_args() > 6) { + // we are calling with an id token + return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args()); + } + + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table'])); + } + + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope')); + } + + /** + * @param string $code + * @param mixed $client_id + * @param mixed $user_id + * @param string $redirect_uri + * @param string $expires + * @param string $scope + * @param string $id_token + * @return bool + */ + private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table'])); + } + + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token')); + } + + /** + * @param string $code + * @return bool + */ + public function expireAuthorizationCode($code) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table'])); + + return $stmt->execute(compact('code')); + } + + /** + * @param string $username + * @param string $password + * @return bool + */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + /** + * @param string $username + * @return array|bool + */ + public function getUserDetails($username) + { + return $this->getUser($username); + } + + /** + * @param mixed $user_id + * @param string $claims + * @return array|bool + */ + public function getUserClaims($user_id, $claims) + { + if (!$userDetails = $this->getUserDetails($user_id)) { + return false; + } + + $claims = explode(' ', trim($claims)); + $userClaims = array(); + + // for each requested claim, if the user has the claim, set it in the response + $validClaims = explode(' ', self::VALID_CLAIMS); + foreach ($validClaims as $validClaim) { + if (in_array($validClaim, $claims)) { + if ($validClaim == 'address') { + // address is an object with subfields + $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); + } else { + $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); + } + } + } + + return $userClaims; + } + + /** + * @param string $claim + * @param array $userDetails + * @return array + */ + protected function getUserClaim($claim, $userDetails) + { + $userClaims = array(); + $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); + $claimValues = explode(' ', $claimValuesString); + + foreach ($claimValues as $value) { + $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; + } + + return $userClaims; + } + + /** + * @param string $refresh_token + * @return bool|mixed + */ + public function getRefreshToken($refresh_token) + { + $stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); + + $token = $stmt->execute(compact('refresh_token')); + if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // convert expires to epoch time + $token['expires'] = strtotime($token['expires']); + } + + return $token; + } + + /** + * @param string $refresh_token + * @param mixed $client_id + * @param mixed $user_id + * @param string $expires + * @param string $scope + * @return bool + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + // convert expires to datestring + $expires = date('Y-m-d H:i:s', $expires); + + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (refresh_token, client_id, user_id, expires, scope) VALUES (:refresh_token, :client_id, :user_id, :expires, :scope)', $this->config['refresh_token_table'])); + + return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope')); + } + + /** + * @param string $refresh_token + * @return bool + */ + public function unsetRefreshToken($refresh_token) + { + $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); + + $stmt->execute(compact('refresh_token')); + + return $stmt->rowCount() > 0; + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param array $user + * @param string $password + * @return bool + */ + protected function checkPassword($user, $password) + { + return $user['password'] == $this->hashPassword($password); + } + + // use a secure hashing algorithm when storing passwords. Override this for your application + protected function hashPassword($password) + { + return sha1($password); + } + + /** + * @param string $username + * @return array|bool + */ + public function getUser($username) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT * from %s where username=:username', $this->config['user_table'])); + $stmt->execute(array('username' => $username)); + + if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username + ), $userInfo); + } + + /** + * plaintext passwords are bad! Override this for your application + * + * @param string $username + * @param string $password + * @param string $firstName + * @param string $lastName + * @return bool + */ + public function setUser($username, $password, $firstName = null, $lastName = null) + { + // do not store in plaintext + $password = $this->hashPassword($password); + + // if it exists, update it. + if ($this->getUser($username)) { + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET password=:password, first_name=:firstName, last_name=:lastName where username=:username', $this->config['user_table'])); + } else { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table'])); + } + + return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); + } + + /** + * @param string $scope + * @return bool + */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + $whereIn = implode(',', array_fill(0, count($scope), '?')); + $stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn)); + $stmt->execute($scope); + + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['count'] == count($scope); + } + + return false; + } + + /** + * @param mixed $client_id + * @return null|string + */ + public function getDefaultScope($client_id = null) + { + $stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table'])); + $stmt->execute(array('is_default' => true)); + + if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) { + $defaultScope = array_map(function ($row) { + return $row['scope']; + }, $result); + + return implode(' ', $defaultScope); + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @return string + */ + public function getClientKey($client_id, $subject) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table'])); + + $stmt->execute(array('client_id' => $client_id, 'subject' => $subject)); + + return $stmt->fetchColumn(); + } + + /** + * @param mixed $client_id + * @return bool|null + */ + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @param $audience + * @param $expires + * @param $jti + * @return array|null + */ + public function getJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT * FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table'])); + + $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return array( + 'issuer' => $result['issuer'], + 'subject' => $result['subject'], + 'audience' => $result['audience'], + 'expires' => $result['expires'], + 'jti' => $result['jti'], + ); + } + + return null; + } + + /** + * @param mixed $client_id + * @param $subject + * @param $audience + * @param $expires + * @param $jti + * @return bool + */ + public function setJti($client_id, $subject, $audience, $expires, $jti) + { + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table'])); + + return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); + } + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPublicKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['public_key']; + } + } + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPrivateKey($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['private_key']; + } + } + + /** + * @param mixed $client_id + * @return string + */ + public function getEncryptionAlgorithm($client_id = null) + { + $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); + + $stmt->execute(compact('client_id')); + if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $result['encryption_algorithm']; + } + + return 'RS256'; + } + + /** + * DDL to create OAuth2 database and tables for PDO storage + * + * @see https://github.com/dsquier/oauth2-server-php-mysql + * + * @param string $dbName + * @return string + */ + public function getBuildSql($dbName = 'oauth2_server_php') + { + $sql = " + CREATE TABLE {$this->config['client_table']} ( + client_id VARCHAR(80) NOT NULL, + client_secret VARCHAR(80), + redirect_uri VARCHAR(2000), + grant_types VARCHAR(80), + scope VARCHAR(4000), + user_id VARCHAR(80), + PRIMARY KEY (client_id) + ); + + CREATE TABLE {$this->config['access_token_table']} ( + access_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (access_token) + ); + + CREATE TABLE {$this->config['code_table']} ( + authorization_code VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + redirect_uri VARCHAR(2000), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + id_token VARCHAR(1000), + PRIMARY KEY (authorization_code) + ); + + CREATE TABLE {$this->config['refresh_token_table']} ( + refresh_token VARCHAR(40) NOT NULL, + client_id VARCHAR(80) NOT NULL, + user_id VARCHAR(80), + expires TIMESTAMP NOT NULL, + scope VARCHAR(4000), + PRIMARY KEY (refresh_token) + ); + + CREATE TABLE {$this->config['user_table']} ( + username VARCHAR(80), + password VARCHAR(80), + first_name VARCHAR(80), + last_name VARCHAR(80), + email VARCHAR(80), + email_verified BOOLEAN, + scope VARCHAR(4000) + ); + + CREATE TABLE {$this->config['scope_table']} ( + scope VARCHAR(80) NOT NULL, + is_default BOOLEAN, + PRIMARY KEY (scope) + ); + + CREATE TABLE {$this->config['jwt_table']} ( + client_id VARCHAR(80) NOT NULL, + subject VARCHAR(80), + public_key VARCHAR(2000) NOT NULL + ); + + CREATE TABLE {$this->config['jti_table']} ( + issuer VARCHAR(80) NOT NULL, + subject VARCHAR(80), + audiance VARCHAR(80), + expires TIMESTAMP NOT NULL, + jti VARCHAR(2000) NOT NULL + ); + + CREATE TABLE {$this->config['public_key_table']} ( + client_id VARCHAR(80), + public_key VARCHAR(2000), + private_key VARCHAR(2000), + encryption_algorithm VARCHAR(100) DEFAULT 'RS256' + ) + "; + + return $sql; + } +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php new file mode 100644 index 00000000..a6dc49fb --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/PublicKeyInterface.php @@ -0,0 +1,30 @@ + + */ +interface PublicKeyInterface +{ + /** + * @param mixed $client_id + * @return mixed + */ + public function getPublicKey($client_id = null); + + /** + * @param mixed $client_id + * @return mixed + */ + public function getPrivateKey($client_id = null); + + /** + * @param mixed $client_id + * @return mixed + */ + public function getEncryptionAlgorithm($client_id = null); +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php new file mode 100644 index 00000000..e6294e22 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php @@ -0,0 +1,321 @@ + + * $storage = new OAuth2\Storage\Redis($redis); + * $storage->setClientDetails($client_id, $client_secret, $redirect_uri); + * + */ +class Redis implements AuthorizationCodeInterface, + AccessTokenInterface, + ClientCredentialsInterface, + UserCredentialsInterface, + RefreshTokenInterface, + JwtBearerInterface, + ScopeInterface, + OpenIDAuthorizationCodeInterface +{ + + private $cache; + + /* The redis client */ + protected $redis; + + /* Configuration array */ + protected $config; + + /** + * Redis Storage! + * + * @param \Predis\Client $redis + * @param array $config + */ + public function __construct($redis, $config=array()) + { + $this->redis = $redis; + $this->config = array_merge(array( + 'client_key' => 'oauth_clients:', + 'access_token_key' => 'oauth_access_tokens:', + 'refresh_token_key' => 'oauth_refresh_tokens:', + 'code_key' => 'oauth_authorization_codes:', + 'user_key' => 'oauth_users:', + 'jwt_key' => 'oauth_jwt:', + 'scope_key' => 'oauth_scopes:', + ), $config); + } + + protected function getValue($key) + { + if ( isset($this->cache[$key]) ) { + return $this->cache[$key]; + } + $value = $this->redis->get($key); + if ( isset($value) ) { + return json_decode($value, true); + } else { + return false; + } + } + + protected function setValue($key, $value, $expire=0) + { + $this->cache[$key] = $value; + $str = json_encode($value); + if ($expire > 0) { + $seconds = $expire - time(); + $ret = $this->redis->setex($key, $seconds, $str); + } else { + $ret = $this->redis->set($key, $str); + } + + // check that the key was set properly + // if this fails, an exception will usually thrown, so this step isn't strictly necessary + return is_bool($ret) ? $ret : $ret->getPayload() == 'OK'; + } + + protected function expireValue($key) + { + unset($this->cache[$key]); + + return $this->redis->del($key); + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + return $this->getValue($this->config['code_key'] . $code); + } + + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + return $this->setValue( + $this->config['code_key'] . $authorization_code, + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + $expires + ); + } + + public function expireAuthorizationCode($code) + { + $key = $this->config['code_key'] . $code; + unset($this->cache[$key]); + + return $this->expireValue($key); + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + $user = $this->getUserDetails($username); + + return $user && $user['password'] === $password; + } + + public function getUserDetails($username) + { + return $this->getUser($username); + } + + public function getUser($username) + { + if (!$userInfo = $this->getValue($this->config['user_key'] . $username)) { + return false; + } + + // the default behavior is to use "username" as the user_id + return array_merge(array( + 'user_id' => $username, + ), $userInfo); + } + + public function setUser($username, $password, $first_name = null, $last_name = null) + { + return $this->setValue( + $this->config['user_key'] . $username, + compact('username', 'password', 'first_name', 'last_name') + ); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return isset($client['client_secret']) + && $client['client_secret'] == $client_secret; + } + + public function isPublicClient($client_id) + { + if (!$client = $this->getClientDetails($client_id)) { + return false; + } + + return empty($client['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + return $this->getValue($this->config['client_key'] . $client_id); + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + return $this->setValue( + $this->config['client_key'] . $client_id, + compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id') + ); + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, (array) $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + return $this->getValue($this->config['refresh_token_key'] . $refresh_token); + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['refresh_token_key'] . $refresh_token, + compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + public function unsetRefreshToken($refresh_token) + { + $result = $this->expireValue($this->config['refresh_token_key'] . $refresh_token); + + return $result > 0; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + return $this->getValue($this->config['access_token_key'].$access_token); + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + return $this->setValue( + $this->config['access_token_key'].$access_token, + compact('access_token', 'client_id', 'user_id', 'expires', 'scope'), + $expires + ); + } + + public function unsetAccessToken($access_token) + { + $result = $this->expireValue($this->config['access_token_key'] . $access_token); + + return $result > 0; + } + + /* ScopeInterface */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + + $result = $this->getValue($this->config['scope_key'].'supported:global'); + + $supportedScope = explode(' ', (string) $result); + + return (count(array_diff($scope, $supportedScope)) == 0); + } + + public function getDefaultScope($client_id = null) + { + if (is_null($client_id) || !$result = $this->getValue($this->config['scope_key'].'default:'.$client_id)) { + $result = $this->getValue($this->config['scope_key'].'default:global'); + } + + return $result; + } + + public function setScope($scope, $client_id = null, $type = 'supported') + { + if (!in_array($type, array('default', 'supported'))) { + throw new \InvalidArgumentException('"$type" must be one of "default", "supported"'); + } + + if (is_null($client_id)) { + $key = $this->config['scope_key'].$type.':global'; + } else { + $key = $this->config['scope_key'].$type.':'.$client_id; + } + + return $this->setValue($key, $scope); + } + + /*JWTBearerInterface */ + public function getClientKey($client_id, $subject) + { + if (!$jwt = $this->getValue($this->config['jwt_key'] . $client_id)) { + return false; + } + + if (isset($jwt['subject']) && $jwt['subject'] == $subject) { + return $jwt['key']; + } + + return null; + } + + public function setClientKey($client_id, $key, $subject = null) + { + return $this->setValue($this->config['jwt_key'] . $client_id, array( + 'key' => $key, + 'subject' => $subject + )); + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('getJti() for the Redis driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs redis implementation. + throw new \Exception('setJti() for the Redis driver is currently unimplemented.'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php new file mode 100644 index 00000000..e6407e44 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/RefreshTokenInterface.php @@ -0,0 +1,82 @@ + + */ +interface RefreshTokenInterface +{ + /** + * Grant refresh access tokens. + * + * Retrieve the stored data for the given refresh token. + * + * Required for OAuth2::GRANT_TYPE_REFRESH_TOKEN. + * + * @param $refresh_token + * Refresh token to be check with. + * + * @return + * An associative array as below, and NULL if the refresh_token is + * invalid: + * - refresh_token: Refresh token identifier. + * - client_id: Client identifier. + * - user_id: User identifier. + * - expires: Expiration unix timestamp, or 0 if the token doesn't expire. + * - scope: (optional) Scope values in space-separated string. + * + * @see http://tools.ietf.org/html/rfc6749#section-6 + * + * @ingroup oauth2_section_6 + */ + public function getRefreshToken($refresh_token); + + /** + * Take the provided refresh token values and store them somewhere. + * + * This function should be the storage counterpart to getRefreshToken(). + * + * 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_REFRESH_TOKEN. + * + * @param $refresh_token + * Refresh token to be stored. + * @param $client_id + * Client identifier to be stored. + * @param $user_id + * User identifier to be stored. + * @param $expires + * Expiration timestamp to be stored. 0 if the token doesn't expire. + * @param $scope + * (optional) Scopes to be stored in space-separated string. + * + * @ingroup oauth2_section_6 + */ + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null); + + /** + * Expire a used refresh token. + * + * This is not explicitly required in the spec, but is almost implied. + * After granting a new refresh token, the old one is no longer useful and + * so should be forcibly expired in the data store so it can't be used again. + * + * 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. + * + * @param $refresh_token + * Refresh token to be expired. + * + * @ingroup oauth2_section_6 + */ + public function unsetRefreshToken($refresh_token); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php new file mode 100644 index 00000000..a8292269 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php @@ -0,0 +1,46 @@ + + */ +interface ScopeInterface +{ + /** + * Check if the provided scope exists. + * + * @param $scope + * A space-separated string of scopes. + * + * @return + * TRUE if it exists, FALSE otherwise. + */ + public function scopeExists($scope); + + /** + * The default scope to use in the event the client + * does not request one. By returning "false", a + * request_error is returned by the server to force a + * scope request by the client. By returning "null", + * opt out of requiring scopes + * + * @param $client_id + * An optional client id that can be used to return customized default scopes. + * + * @return + * string representation of default scope, null if + * scopes are not defined, or false to force scope + * request by the client + * + * ex: + * 'default' + * ex: + * null + */ + public function getDefaultScope($client_id = null); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php new file mode 100644 index 00000000..f550579e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/UserCredentialsInterface.php @@ -0,0 +1,52 @@ + + */ +interface UserCredentialsInterface +{ + /** + * Grant access tokens for basic user credentials. + * + * Check the supplied username and password for validity. + * + * You can also use the $client_id param to do any checks required based + * on a client, if you need that. + * + * Required for OAuth2::GRANT_TYPE_USER_CREDENTIALS. + * + * @param $username + * Username to be check with. + * @param $password + * Password to be check with. + * + * @return + * TRUE if the username and password are valid, and FALSE if it isn't. + * Moreover, if the username and password are valid, and you want to + * + * @see http://tools.ietf.org/html/rfc6749#section-4.3 + * + * @ingroup oauth2_section_4 + */ + public function checkUserCredentials($username, $password); + + /** + * @param string $username - username to get details for + * @return array|false - the associated "user_id" and optional "scope" values + * This function MUST return FALSE if the requested user does not exist or is + * invalid. "scope" is a space-separated list of restricted scopes. + * @code + * return array( + * "user_id" => USER_ID, // REQUIRED user_id to be stored with the authorization code or access token + * "scope" => SCOPE // OPTIONAL space-separated list of restricted scopes + * ); + * @endcode + */ + public function getUserDetails($username); +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php new file mode 100644 index 00000000..8ac8596a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php @@ -0,0 +1,130 @@ +config = array_merge(array( + 'token_param_name' => 'access_token', + 'token_bearer_header_name' => 'Bearer', + ), $config); + } + + public function getTokenType() + { + return 'Bearer'; + } + + /** + * Check if the request has supplied token + * + * @see https://github.com/bshaffer/oauth2-server-php/issues/349#issuecomment-37993588 + */ + public function requestHasToken(RequestInterface $request) + { + $headers = $request->headers('AUTHORIZATION'); + + // check the header, then the querystring, then the request body + return !empty($headers) || (bool) ($request->request($this->config['token_param_name'])) || (bool) ($request->query($this->config['token_param_name'])); + } + + /** + * This is a convenience function that can be used to get the token, which can then + * be passed to getAccessTokenData(). The constraints specified by the draft are + * attempted to be adheared to in this method. + * + * As per the Bearer spec (draft 8, section 2) - there are three ways for a client + * to specify the bearer token, in order of preference: Authorization Header, + * POST and GET. + * + * NB: Resource servers MUST accept tokens via the Authorization scheme + * (http://tools.ietf.org/html/rfc6750#section-2). + * + * @todo Should we enforce TLS/SSL in this function? + * + * @see http://tools.ietf.org/html/rfc6750#section-2.1 + * @see http://tools.ietf.org/html/rfc6750#section-2.2 + * @see http://tools.ietf.org/html/rfc6750#section-2.3 + * + * Old Android version bug (at least with version 2.2) + * @see http://code.google.com/p/android/issues/detail?id=6684 + * + */ + public function getAccessTokenParameter(RequestInterface $request, ResponseInterface $response) + { + $headers = $request->headers('AUTHORIZATION'); + + /** + * Ensure more than one method is not used for including an + * access token + * + * @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + $methodsUsed = !empty($headers) + (bool) ($request->query($this->config['token_param_name'])) + (bool) ($request->request($this->config['token_param_name'])); + if ($methodsUsed > 1) { + $response->setError(400, 'invalid_request', 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); + + return null; + } + + /** + * If no authentication is provided, set the status code + * to 401 and return no other error information + * + * @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + if ($methodsUsed == 0) { + $response->setStatusCode(401); + + return null; + } + + // HEADER: Get the access token from the header + if (!empty($headers)) { + if (!preg_match('/' . $this->config['token_bearer_header_name'] . '\s(\S+)/i', $headers, $matches)) { + $response->setError(400, 'invalid_request', 'Malformed auth header'); + + return null; + } + + return $matches[1]; + } + + if ($request->request($this->config['token_param_name'])) { + // // POST: Get the token from POST data + if (!in_array(strtolower($request->server('REQUEST_METHOD')), array('post', 'put'))) { + $response->setError(400, 'invalid_request', 'When putting the token in the body, the method must be POST or PUT', '#section-2.2'); + + return null; + } + + $contentType = $request->server('CONTENT_TYPE'); + if (false !== $pos = strpos($contentType, ';')) { + $contentType = substr($contentType, 0, $pos); + } + + if ($contentType !== null && $contentType != 'application/x-www-form-urlencoded') { + // IETF specifies content-type. NB: Not all webservers populate this _SERVER variable + // @see http://tools.ietf.org/html/rfc6750#section-2.2 + $response->setError(400, 'invalid_request', 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + + return null; + } + + return $request->request($this->config['token_param_name']); + } + + // GET method + return $request->query($this->config['token_param_name']); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php new file mode 100644 index 00000000..fe6a86aa --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Mac.php @@ -0,0 +1,22 @@ +assertTrue(class_exists('OAuth2\Server')); + $this->assertTrue(class_exists('OAuth2\Request')); + $this->assertTrue(class_exists('OAuth2\Response')); + $this->assertTrue(class_exists('OAuth2\GrantType\UserCredentials')); + $this->assertTrue(interface_exists('OAuth2\Storage\AccessTokenInterface')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php new file mode 100644 index 00000000..fe3553b2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php @@ -0,0 +1,493 @@ +getTestServer(); + $request = new Request(); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'No client id supplied'); + } + + public function testInvalidClientIdResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Fake Client ID', // invalid client id + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client id supplied is invalid'); + } + + public function testNoRedirectUriSuppliedOrStoredResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'No redirect URI was supplied or stored'); + } + + public function testNoResponseTypeResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'Invalid or missing response type'); + } + + public function testInvalidResponseTypeResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'invalid', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'Invalid or missing response type'); + } + + public function testRedirectUriFragmentResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com#fragment', // valid redirect URI + 'response_type' => 'code', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI must not contain a fragment'); + } + + public function testEnforceState() + { + $server = $this->getTestServer(array('enforce_state' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_request'); + $this->assertEquals($query['error_description'], 'The state parameter is required'); + } + + public function testDoNotEnforceState() + { + $server = $this->getTestServer(array('enforce_state' => false)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNotContains('error', $response->getHttpHeader('Location')); + } + + public function testEnforceScope() + { + $server = $this->getTestServer(); + $scopeStorage = new Memory(array('default_scope' => false, 'supported_scopes' => array('testscope'))); + $server->setScopeUtil(new Scope($scopeStorage)); + + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'invalid_client'); + $this->assertEquals($query['error_description'], 'This application requires you specify a scope parameter'); + + $request->query['scope'] = 'testscope'; + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNotContains('error', $response->getHttpHeader('Location')); + } + + public function testInvalidRedirectUri() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'redirect_uri' => 'http://adobe.com', // invalid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testInvalidRedirectUriApprovedByBuggyRegisteredUri() + { + $server = $this->getTestServer(); + $server->setConfig('require_exact_redirect_uri', false); + $request = new Request(array( + 'client_id' => 'Test Client ID with Buggy Redirect Uri', // valid client id + 'redirect_uri' => 'http://adobe.com', // invalid redirect URI + 'response_type' => 'code', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testNoRedirectUriWithMultipleRedirectUris() + { + $server = $this->getTestServer(); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Multiple Redirect Uris', // valid client id + 'response_type' => 'code', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_uri'); + $this->assertEquals($response->getParameter('error_description'), 'A redirect URI must be supplied when multiple redirect URIs are registered'); + } + + public function testRedirectUriWithValidRedirectUri() + { + $server = $this->getTestServer(); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + public function testRedirectUriWithDifferentQueryAndExactMatchRequired() + { + $server = $this->getTestServer(array('require_exact_redirect_uri' => true)); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'redirect_uri_mismatch'); + $this->assertEquals($response->getParameter('error_description'), 'The redirect URI provided is missing or does not match'); + } + + public function testRedirectUriWithDifferentQueryAndExactMatchNotRequired() + { + $server = $this->getTestServer(array('require_exact_redirect_uri' => false)); + + // create a request with no "redirect_uri" in querystring + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri Parts', // valid client id + 'response_type' => 'code', + 'redirect_uri' => 'http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true&hereisa=querystring', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + public function testMultipleRedirectUris() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Multiple Redirect Uris', // valid client id + 'redirect_uri' => 'http://brentertainment.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz' + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + + // call again with different (but still valid) redirect URI + $request->query['redirect_uri'] = 'http://morehazards.com'; + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('code', $response->getHttpHeader('Location')); + } + + /** + * @see http://tools.ietf.org/html/rfc6749#section-4.1.3 + * @see https://github.com/bshaffer/oauth2-server-php/issues/163 + */ + public function testNoRedirectUriSuppliedDoesNotRequireTokenRedirectUri() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'response_type' => 'code', + 'state' => 'xyz', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 302); + $this->assertContains('state', $response->getHttpHeader('Location')); + $this->assertStringStartsWith('http://brentertainment.com?code=', $response->getHttpHeader('Location')); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + + // call token endpoint with no redirect_uri supplied + $request = TestRequest::createPost(array( + 'client_id' => 'Test Client ID with Redirect Uri', // valid client id + 'client_secret' => 'TestSecret2', + 'grant_type' => 'authorization_code', + 'code' => $query['code'], + )); + + $server->handleTokenRequest($request, $response = new Response(), true); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNotNull($response->getParameter('access_token')); + } + + public function testUserDeniesAccessResponse() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'access_denied'); + $this->assertEquals($query['error_description'], 'The user denied access to your application'); + } + + public function testCodeQueryParamIsSet() + { + $server = $this->getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + + $this->assertEquals('http', $parts['scheme']); // same as passed in to redirect_uri + $this->assertEquals('adobe.com', $parts['host']); // same as passed in to redirect_uri + $this->assertArrayHasKey('query', $parts); + $this->assertFalse(isset($parts['fragment'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $query); + $this->assertNotNull($query); + $this->assertArrayHasKey('code', $query); + + // ensure no id_token was saved, since the openid scope wasn't requested + $storage = $server->getStorage('authorization_code'); + $code = $storage->getAuthorizationCode($query['code']); + $this->assertTrue(empty($code['id_token'])); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + } + + public function testSuccessfulRequestReturnsStateParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'test', // valid state string (just needs to be passed back to us) + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + parse_str($parts['query'], $query); + + $this->assertArrayHasKey('state', $query); + $this->assertEquals($query['state'], 'test'); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + } + + public function testSuccessfulRequestStripsExtraParameters() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'code', + 'state' => 'test', // valid state string (just needs to be passed back to us) + 'fake' => 'something', // extra query param + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertFalse(isset($parts['fake'])); + $this->assertArrayHasKey('query', $parts); + parse_str($parts['query'], $query); + + $this->assertFalse(isset($parmas['fake'])); + $this->assertArrayHasKey('state', $query); + $this->assertEquals($query['state'], 'test'); + } + + public function testSuccessfulOpenidConnectRequest() + { + $server = $this->getTestServer(array( + 'use_openid_connect' => true, + 'issuer' => 'bojanz', + )); + + $request = new Request(array( + 'client_id' => 'Test Client ID', + 'redirect_uri' => 'http://adobe.com', + 'response_type' => 'code', + 'state' => 'xyz', + 'scope' => 'openid', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + $this->assertFalse(isset($parts['fragment'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $query); + $this->assertNotNull($query); + $this->assertArrayHasKey('code', $query); + + // ensure no error was returned + $this->assertFalse(isset($query['error'])); + $this->assertFalse(isset($query['error_description'])); + + // confirm that the id_token has been created. + $storage = $server->getStorage('authorization_code'); + $code = $storage->getAuthorizationCode($query['code']); + $this->assertTrue(!empty($code['id_token'])); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $controller = new AuthorizeController($storage); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php new file mode 100644 index 00000000..cd54d239 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/ResourceControllerTest.php @@ -0,0 +1,177 @@ +getTestServer(); + $request = Request::createFromGlobals(); + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertEquals('', $response->getResponseBody()); + } + + public function testMalformedHeader() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'tH1s i5 B0gU5'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Malformed auth header'); + } + + public function testMultipleTokensSubmitted() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->request['access_token'] = 'TEST'; + $request->query['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Only one method may be used to authenticate at a time (Auth header, GET or POST)'); + } + + public function testInvalidRequestMethod() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->server['REQUEST_METHOD'] = 'GET'; + $request->request['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'When putting the token in the body, the method must be POST or PUT'); + } + + public function testInvalidContentType() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->server['REQUEST_METHOD'] = 'POST'; + $request->server['CONTENT_TYPE'] = 'application/json'; + $request->request['access_token'] = 'TEST'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + } + + public function testInvalidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer TESTTOKEN'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_token'); + $this->assertEquals($response->getParameter('error_description'), 'The access token provided is invalid'); + } + + public function testExpiredToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-expired'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_token'); + $this->assertEquals($response->getParameter('error_description'), 'The access token provided has expired'); + } + + public function testOutOfScopeToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $scope = 'outofscope'; + $allow = $server->verifyResourceRequest($request, $response = new Response(), $scope); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 403); + $this->assertEquals($response->getParameter('error'), 'insufficient_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The request requires higher privileges than provided by the access token'); + + // verify the "scope" has been set in the "WWW-Authenticate" header + preg_match('/scope="(.*?)"/', $response->getHttpHeader('WWW-Authenticate'), $matches); + $this->assertEquals(2, count($matches)); + $this->assertEquals($matches[1], 'outofscope'); + } + + public function testMalformedToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-malformed'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertFalse($allow); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'malformed_token'); + $this->assertEquals($response->getParameter('error_description'), 'Malformed token (missing "expires")'); + } + + public function testValidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertTrue($allow); + } + + public function testValidTokenWithScopeParam() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-scope'; + $request->query['scope'] = 'testscope'; + $allow = $server->verifyResourceRequest($request, $response = new Response()); + $this->assertTrue($allow); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $tokenType = new \OAuth2\TokenType\Bearer(); + $controller = new ResourceController($tokenType, $storage); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php new file mode 100644 index 00000000..d18eaa6d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/TokenControllerTest.php @@ -0,0 +1,332 @@ +getTestServer(); + $server->handleTokenRequest(TestRequest::createPost(), $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The grant type was not specified in the request'); + } + + public function testInvalidGrantType() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'invalid_grant_type', // invalid grant type + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'unsupported_grant_type'); + $this->assertEquals($response->getParameter('error_description'), 'Grant type "invalid_grant_type" not supported'); + } + + public function testNoClientId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'Client credentials were not found in the headers or body'); + } + + public function testNoClientSecretWithConfidentialClient() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'This client is invalid or must authenticate using a client secret'); + } + + public function testNoClientSecretWithEmptySecret() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode-empty-secret', + 'client_id' => 'Test Client ID Empty Secret', // valid client id + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + } + + public function testInvalidClientId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Fake Client ID', // invalid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testInvalidClientSecret() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'Fake Client Secret', // invalid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testValidTokenResponse() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertNotNull($response->getParameter('access_token')); + $this->assertNotNull($response->getParameter('expires_in')); + $this->assertNotNull($response->getParameter('token_type')); + } + + public function testValidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope1 clientscope2' + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertEquals('clientscope1 clientscope2', $response->getParameter('scope')); + } + + public function testInvalidClientIdScope() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'code' => 'testcode-with-scope', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'clientscope3' + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testEnforceScope() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($storage)); + + $scope = new Scope(array( + 'default_scope' => false, + 'supported_scopes' => array('testscope') + )); + $server->setScopeUtil($scope); + + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $response = $server->handleTokenRequest($request); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'This application requires you specify a scope parameter'); + } + + public function testCanReceiveAccessTokenUsingPasswordGrantTypeWithoutClientSecret() + { + // add the test parameters in memory + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new UserCredentials($storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID For Password Grant', // valid client id + 'username' => 'johndoe', // valid username + 'password' => 'password', // valid password for username + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + $this->assertNotNull($response->getParameter('access_token')); + $this->assertNotNull($response->getParameter('expires_in')); + $this->assertNotNull($response->getParameter('token_type')); + } + + public function testInvalidTokenTypeHintForRevoke() + { + $server = $this->getTestServer(); + + $request = TestRequest::createPost(array( + 'token_type_hint' => 'foo', + 'token' => 'sometoken' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + + $this->assertTrue($response instanceof Response); + $this->assertEquals(400, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Token type hint must be either \'access_token\' or \'refresh_token\''); + } + + public function testMissingTokenForRevoke() + { + $server = $this->getTestServer(); + + $request = TestRequest::createPost(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(400, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing token parameter to revoke'); + } + + public function testInvalidRequestMethodForRevoke() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setQuery(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(405, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The request method must be POST when revoking an access token'); + } + + public function testCanUseCrossOriginRequestForRevoke() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setMethod('OPTIONS'); + + $server->handleRevokeRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getHttpHeader('Allow'), 'POST, OPTIONS'); + } + + public function testInvalidRequestMethodForAccessToken() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setQuery(array( + 'token_type_hint' => 'access_token' + )); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(405, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The request method must be POST when requesting an access token'); + } + + public function testCanUseCrossOriginRequestForAccessToken() + { + $server = $this->getTestServer(); + + $request = new TestRequest(); + $request->setMethod('OPTIONS'); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertTrue($response instanceof Response); + $this->assertEquals(200, $response->getStatusCode(), var_export($response, 1)); + $this->assertEquals($response->getHttpHeader('Allow'), 'POST, OPTIONS'); + } + + public function testCreateController() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $accessToken = new \OAuth2\ResponseType\AccessToken($storage); + $controller = new TokenController($accessToken, $storage); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php new file mode 100644 index 00000000..c7e92c05 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php @@ -0,0 +1,103 @@ +privateKey = << $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + + // test BC behaviour of trusting the algorithm in the header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + + // test BC behaviour of not verifying by passing false + $payload = $jwtUtil->decode($encoded, $client_key, false); + $this->assertEquals($params, $payload); + + // test the new restricted algorithms header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + } + + public function testInvalidJwt() + { + $jwtUtil = new FirebaseJwt(); + + $this->assertFalse($jwtUtil->decode('goob')); + $this->assertFalse($jwtUtil->decode('go.o.b')); + } + + /** @dataProvider provideClientCredentials */ + public function testInvalidJwtHeader($client_id, $client_key) + { + $jwtUtil = new FirebaseJwt(); + + $params = array( + 'iss' => $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + // testing for algorithm tampering when only RSA256 signing is allowed + // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + $tampered = $jwtUtil->encode($params, $client_key, 'HS256'); + + $payload = $jwtUtil->decode($tampered, $client_key, array('RS256')); + + $this->assertFalse($payload); + } + + public function provideClientCredentials() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $client_id = 'Test Client ID'; + $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com"); + + return array( + array($client_id, $client_key), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php new file mode 100644 index 00000000..d73b4c92 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/JwtTest.php @@ -0,0 +1,103 @@ +privateKey = << $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + $encoded = $jwtUtil->encode($params, $this->privateKey, 'RS256'); + + // test BC behaviour of trusting the algorithm in the header + $payload = $jwtUtil->decode($encoded, $client_key); + $this->assertEquals($params, $payload); + + // test BC behaviour of not verifying by passing false + $payload = $jwtUtil->decode($encoded, $client_key, false); + $this->assertEquals($params, $payload); + + // test the new restricted algorithms header + $payload = $jwtUtil->decode($encoded, $client_key, array('RS256')); + $this->assertEquals($params, $payload); + } + + public function testInvalidJwt() + { + $jwtUtil = new Jwt(); + + $this->assertFalse($jwtUtil->decode('goob')); + $this->assertFalse($jwtUtil->decode('go.o.b')); + } + + /** @dataProvider provideClientCredentials */ + public function testInvalidJwtHeader($client_id, $client_key) + { + $jwtUtil = new Jwt(); + + $params = array( + 'iss' => $client_id, + 'exp' => time() + 1000, + 'iat' => time(), + 'sub' => 'testuser@ourdomain.com', + 'aud' => 'http://myapp.com/oauth/auth', + 'scope' => null, + ); + + // testing for algorithm tampering when only RSA256 signing is allowed + // @see https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ + $tampered = $jwtUtil->encode($params, $client_key, 'HS256'); + + $payload = $jwtUtil->decode($tampered, $client_key, array('RS256')); + + $this->assertFalse($payload); + } + + public function provideClientCredentials() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $client_id = 'Test Client ID'; + $client_key = $storage->getClientKey($client_id, "testuser@ourdomain.com"); + + return array( + array($client_id, $client_key), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php new file mode 100644 index 00000000..b2314ffc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/AuthorizationCodeTest.php @@ -0,0 +1,224 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameter: "code" is required'); + } + + public function testInvalidCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'InvalidCode', // invalid authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Authorization code doesn\'t exist or is invalid for the client'); + } + + public function testCodeCannotBeUsedTwice() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNotNull($response->getParameter('access_token')); + + // try to use the same code again + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Authorization code doesn\'t exist or is invalid for the client'); + } + + public function testExpiredCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-expired', // expired authorization code + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'The authorization code has expired'); + } + + public function testValidCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidRedirectUri() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://brentertainment.com/voil%C3%A0', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-redirect-uri', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCodeNoScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1 scope2'); + } + + public function testValidCodeSameScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidCodeLessScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCodeDifferentScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidCodeInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-with-scope', // valid code + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidClientDifferentCode() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'code' => 'testcode', // valid code + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'authorization_code doesn\'t exist or is invalid for the client'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php new file mode 100644 index 00000000..2a7d0eb3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ClientCredentialsTest.php @@ -0,0 +1,160 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'FakeSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'The client credentials are invalid'); + } + + public function testValidCredentials() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('scope', $token); + $this->assertNull($token['scope']); + } + + public function testValidCredentialsWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testValidCredentialsInHeader() + { + // create with HTTP_AUTHORIZATION in header + $server = $this->getTestServer(); + $headers = array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('Test Client ID:TestSecret'), 'REQUEST_METHOD' => 'POST'); + $params = array('grant_type' => 'client_credentials'); + $request = new Request(array(), $params, array(), array(), array(), $headers); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + + // create using PHP Authorization Globals + $headers = array('PHP_AUTH_USER' => 'Test Client ID', 'PHP_AUTH_PW' => 'TestSecret', 'REQUEST_METHOD' => 'POST'); + $params = array('grant_type' => 'client_credentials'); + $request = new Request(array(), $params, array(), array(), array(), $headers); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testValidCredentialsInRequest() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testValidCredentialsInQuerystring() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertNotNull($token['access_token']); + } + + public function testClientUserIdIsSetInAccessToken() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Client ID With User ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + // verify the user_id was associated with the token + $storage = $server->getStorage('client'); + $token = $storage->getAccessToken($token['access_token']); + $this->assertNotNull($token); + $this->assertArrayHasKey('user_id', $token); + $this->assertEquals($token['user_id'], 'brent@brentertainment.com'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php new file mode 100644 index 00000000..14bf980f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/ImplicitTest.php @@ -0,0 +1,144 @@ +getTestServer(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // invalid response type + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'unsupported_response_type'); + $this->assertEquals($query['error_description'], 'implicit grant type not supported'); + } + + public function testUserDeniesAccessResponse() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), false); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + parse_str($parts['query'], $query); + + $this->assertEquals($query['error'], 'access_denied'); + $this->assertEquals($query['error_description'], 'The user denied access to your application'); + } + + public function testSuccessfulRequestFragmentParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'xyz', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + + $this->assertEquals('http', $parts['scheme']); // same as passed in to redirect_uri + $this->assertEquals('adobe.com', $parts['host']); // same as passed in to redirect_uri + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('access_token', $params); + $this->assertArrayHasKey('expires_in', $params); + $this->assertArrayHasKey('token_type', $params); + } + + public function testSuccessfulRequestReturnsStateParameter() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'test', // valid state string (just needs to be passed back to us) + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + parse_str($parts['fragment'], $params); + + $this->assertArrayHasKey('state', $params); + $this->assertEquals($params['state'], 'test'); + } + + public function testSuccessfulRequestStripsExtraParameters() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com?fake=something', // valid redirect URI + 'response_type' => 'token', // valid response type + 'state' => 'test', // valid state string (just needs to be passed back to us) + 'fake' => 'something', // add extra param to querystring + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $this->assertNull($response->getParameter('error')); + $this->assertNull($response->getParameter('error_description')); + + $location = $response->getHttpHeader('Location'); + $parts = parse_url($location); + $this->assertFalse(isset($parts['fake'])); + $this->assertArrayHasKey('fragment', $parts); + parse_str($parts['fragment'], $params); + + $this->assertFalse(isset($params['fake'])); + $this->assertArrayHasKey('state', $params); + $this->assertEquals($params['state'], 'test'); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php new file mode 100644 index 00000000..e60023b3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/JwtBearerTest.php @@ -0,0 +1,361 @@ +privateKey = <<getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get the jwt and break it + $jwt = $this->getJWT(); + $jwt = substr_replace($jwt, 'broken', 3, 6); + + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'JWT is malformed'); + } + + public function testBrokenSignature() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get the jwt and break signature + $jwt = $this->getJWT() . 'notSupposeToBeHere'; + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT failed signature verification'); + } + + public function testExpiredJWT() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get an expired JWT + $jwt = $this->getJWT(1234); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT has expired'); + } + + public function testBadExp() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get an expired JWT + $jwt = $this->getJWT('badtimestamp'); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Expiration (exp) time must be a unix time stamp'); + } + + public function testNoAssert() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Do not pass the assert (JWT) + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "assertion" required'); + } + + public function testNotBefore() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get a future NBF + $jwt = $this->getJWT(null, time() + 10000); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JWT cannot be used before the Not Before (nbf) time'); + } + + public function testBadNotBefore() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + )); + + //Get a non timestamp nbf + $jwt = $this->getJWT(null, 'notatimestamp'); + $request->request['assertion'] = $jwt; + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Not Before (nbf) time must be a unix time stamp'); + } + + public function testNonMatchingAudience() + { + $server = $this->getTestServer('http://google.com/oauth/o/auth'); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid audience (aud)'); + } + + public function testBadClientID() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'bad_client_id'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testBadSubject() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, 'anotheruser@ourdomain,com'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testMissingKey() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Missing Key Cli,nt'), + )); + + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid issuer (iss) or subject (sub) provided'); + } + + public function testValidJwt() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(), // valid assertion + )); + + $token = $server->grantAccessToken($request, new Response()); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidJwtWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID'), // valid assertion + 'scope' => 'scope1', // valid scope + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidJwtInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, null, 'Test Client ID'), // valid assertion + 'scope' => 'invalid-scope', // invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testValidJti() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(null, null, 'testuser@ourdomain.com', 'Test Client ID', 'unused_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testInvalidJti() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', 'used_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); + } + + public function testJtiReplayAttack() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // valid grant type + 'assertion' => $this->getJWT(99999999900, null, 'testuser@ourdomain.com', 'Test Client ID', 'totally_new_jti'), // valid assertion with invalid scope + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + + //Replay the same request + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'JSON Token Identifier (jti) has already been used'); + } + + /** + * Generates a JWT + * @param $exp The expiration date. If the current time is greater than the exp, the JWT is invalid. + * @param $nbf The "not before" time. If the current time is less than the nbf, the JWT is invalid. + * @param $sub The subject we are acting on behalf of. This could be the email address of the user in the system. + * @param $iss The issuer, usually the client_id. + * @return string + */ + private function getJWT($exp = null, $nbf = null, $sub = null, $iss = 'Test Client ID', $jti = null) + { + if (!$exp) { + $exp = time() + 1000; + } + + if (!$sub) { + $sub = "testuser@ourdomain.com"; + } + + $params = array( + 'iss' => $iss, + 'exp' => $exp, + 'iat' => time(), + 'sub' => $sub, + 'aud' => 'http://myapp.com/oauth/auth', + ); + + if ($nbf) { + $params['nbf'] = $nbf; + } + + if ($jti) { + $params['jti'] = $jti; + } + + $jwtUtil = new Jwt(); + + return $jwtUtil->encode($params, $this->privateKey, 'RS256'); + } + + private function getTestServer($audience = 'http://myapp.com/oauth/auth') + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new JwtBearer($storage, $audience, new Jwt())); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php new file mode 100644 index 00000000..c964c6bb --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/RefreshTokenTest.php @@ -0,0 +1,205 @@ +getTestServer(); + $server->addGrantType(new RefreshToken($this->storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameter: "refresh_token" is required'); + } + + public function testInvalidRefreshToken() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'fake-token', // invalid refresh token + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid refresh token'); + } + + public function testValidRefreshTokenWithNewRefreshTokenInResponse() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array('always_issue_new_refresh_token' => true))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $refresh_token = $this->storage->getRefreshToken($token['refresh_token']); + $this->assertNotNull($refresh_token); + $this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']); + $this->assertEquals($refresh_token['client_id'], $request->request('client_id')); + $this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used'); + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertFalse($used_token, 'the refresh token used is no longer valid'); + } + + public function testValidRefreshTokenDoesNotUnsetToken() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array( + 'always_issue_new_refresh_token' => true, + 'unset_refresh_token_after_use' => false, + ))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertNotNull($used_token, 'the refresh token used is still valid'); + } + + public function testValidRefreshTokenWithNoRefreshTokenInResponse() + { + $server = $this->getTestServer(); + $server->addGrantType(new RefreshToken($this->storage, array('always_issue_new_refresh_token' => false))); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertFalse(isset($token['refresh_token']), 'refresh token should not be returned'); + + $used_token = $this->storage->getRefreshToken('test-refreshtoken'); + $this->assertNotNull($used_token, 'the refresh token used is still valid'); + } + + public function testValidRefreshTokenSameScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope2 scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope2 scope1'); + } + + public function testValidRefreshTokenLessScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope1', + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidRefreshTokenDifferentScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'scope3', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidRefreshTokenInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken-with-scope', // valid refresh token (with scope) + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'The scope requested is invalid for this request'); + } + + public function testValidClientDifferentRefreshToken() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Some Other Client', // valid client id + 'client_secret' => 'TestSecret3', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'refresh_token doesn\'t exist or is invalid for the client'); + } + + private function getTestServer() + { + $this->storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($this->storage); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php new file mode 100644 index 00000000..8b725e13 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/GrantType/UserCredentialsTest.php @@ -0,0 +1,173 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'password' => 'testpass', // valid password + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "username" and "password" required'); + } + + public function testNoPassword() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + )); + $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'Missing parameters: "username" and "password" required'); + } + + public function testInvalidUsername() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'fake-username', // valid username + 'password' => 'testpass', // valid password + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid username and password combination'); + } + + public function testInvalidPassword() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'fakepass', // invalid password + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 401); + $this->assertEquals($response->getParameter('error'), 'invalid_grant'); + $this->assertEquals($response->getParameter('error_description'), 'Invalid username and password combination'); + } + + public function testValidCredentials() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testValidCredentialsWithScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'scope1', // valid scope + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('scope', $token); + $this->assertEquals($token['scope'], 'scope1'); + } + + public function testValidCredentialsInvalidScope() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + 'scope' => 'invalid-scope', + )); + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_scope'); + $this->assertEquals($response->getParameter('error_description'), 'An unsupported scope was requested'); + } + + public function testNoSecretWithPublicClient() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID Empty Secret', // valid public client + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + } + + public function testNoSecretWithConfidentialClient() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid public client + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + $token = $server->grantAccessToken($request, $response = new Response()); + + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_client'); + $this->assertEquals($response->getParameter('error_description'), 'This client is invalid or must authenticate using a client secret'); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage); + $server->addGrantType(new UserCredentials($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php new file mode 100644 index 00000000..fdc16dab --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/AuthorizeControllerTest.php @@ -0,0 +1,183 @@ +getTestServer(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'af0ifjsldkj', + 'nonce' => 'n-0S6_WzA2Mj', + )); + + // Test valid id_token request + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('id_token', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayNotHasKey('access_token', $query); + $this->assertArrayNotHasKey('expires_in', $query); + $this->assertArrayNotHasKey('token_type', $query); + + // Test valid token id_token request + $request->query['response_type'] = 'id_token token'; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + + // assert that with multiple-valued response types, order does not matter + $request->query['response_type'] = 'token id_token'; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + + // assert that with multiple-valued response types with extra spaces do not matter + $request->query['response_type'] = ' token id_token '; + $server->handleAuthorizeRequest($request, $response, true); + + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['fragment'], $query); + + $this->assertEquals('n-0S6_WzA2Mj', $server->getAuthorizeController()->getNonce()); + $this->assertEquals($query['state'], 'af0ifjsldkj'); + + $this->assertArrayHasKey('access_token', $query); + $this->assertArrayHasKey('expires_in', $query); + $this->assertArrayHasKey('token_type', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('id_token', $query); + } + + public function testMissingNonce() + { + $server = $this->getTestServer(); + $authorize = $server->getAuthorizeController(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'xyz', + )); + + // Test missing nonce for 'id_token' response type + $server->handleAuthorizeRequest($request, $response, true); + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'invalid_nonce'); + $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter'); + + // Test missing nonce for 'id_token token' response type + $request->query['response_type'] = 'id_token token'; + $server->handleAuthorizeRequest($request, $response, true); + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'invalid_nonce'); + $this->assertEquals($params['error_description'], 'This application requires you specify a nonce parameter'); + } + + public function testNotGrantedApplication() + { + $server = $this->getTestServer(); + + $response = new Response(); + $request = new Request(array( + 'client_id' => 'Test Client ID', // valid client id + 'redirect_uri' => 'http://adobe.com', // valid redirect URI + 'response_type' => 'id_token', + 'state' => 'af0ifjsldkj', + 'nonce' => 'n-0S6_WzA2Mj', + )); + + // Test not approved application + $server->handleAuthorizeRequest($request, $response, false); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'consent_required'); + $this->assertEquals($params['error_description'], 'The user denied access to your application'); + + // Test not approved application with prompt parameter + $request->query['prompt'] = 'none'; + $server->handleAuthorizeRequest($request, $response, false); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'login_required'); + $this->assertEquals($params['error_description'], 'The user must log in'); + + // Test not approved application with user_id set + $request->query['prompt'] = 'none'; + $server->handleAuthorizeRequest($request, $response, false, 'some-user-id'); + + $params = $response->getParameters(); + + $this->assertEquals($params['error'], 'interaction_required'); + $this->assertEquals($params['error_description'], 'The user must grant access to your application'); + } + + public function testNeedsIdToken() + { + $server = $this->getTestServer(); + $authorize = $server->getAuthorizeController(); + + $this->assertTrue($authorize->needsIdToken('openid')); + $this->assertTrue($authorize->needsIdToken('openid profile')); + $this->assertFalse($authorize->needsIdToken('')); + $this->assertFalse($authorize->needsIdToken('some-scope')); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'phpunit', + 'allow_implicit' => true + ); + + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php new file mode 100644 index 00000000..62e84df5 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Controller/UserInfoControllerTest.php @@ -0,0 +1,45 @@ +handleUserInfoRequest(new Request(), $response); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testValidToken() + { + $server = $this->getTestServer(); + $request = Request::createFromGlobals(); + $request->headers['AUTHORIZATION'] = 'Bearer accesstoken-openid-connect'; + $response = new Response(); + + $server->handleUserInfoRequest($request, $response); + $parameters = $response->getParameters(); + $this->assertEquals($parameters['sub'], 'testuser'); + $this->assertEquals($parameters['email'], 'testuser@test.com'); + $this->assertEquals($parameters['email_verified'], true); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php new file mode 100644 index 00000000..c89a1ae9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/GrantType/AuthorizationCodeTest.php @@ -0,0 +1,58 @@ +getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-openid', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('id_token', $token); + $this->assertEquals('test_id_token', $token['id_token']); + + // this is only true if "offline_access" was requested + $this->assertFalse(isset($token['refresh_token'])); + } + + public function testOfflineAccess() + { + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'code' => 'testcode-openid', // valid code + 'scope' => 'offline_access', // valid code + )); + $token = $server->grantAccessToken($request, new Response()); + + $this->assertNotNull($token); + $this->assertArrayHasKey('id_token', $token); + $this->assertEquals('test_id_token', $token['id_token']); + $this->assertTrue(isset($token['refresh_token'])); + } + + private function getTestServer() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, array('use_openid_connect' => true)); + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php new file mode 100644 index 00000000..7b892c94 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/CodeIdTokenTest.php @@ -0,0 +1,183 @@ +getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + 'nonce' => 'test', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + + // only exists if an access token was granted along with the id_token + $this->assertArrayNotHasKey('at_hash', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + public function testUserClaimsWithUserId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $userId = 'testuser'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('email', $claims); + $this->assertArrayHasKey('email_verified', $claims); + $this->assertNotNull($claims['email']); + $this->assertNotNull($claims['email_verified']); + } + + public function testUserClaimsWithoutUserId() + { + // add the test parameters in memory + $server = $this->getTestServer(); + + $request = new Request(array( + 'response_type' => 'code id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $userId = null; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userId); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('query', $parts); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['query'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('code', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayNotHasKey('email', $claims); + $this->assertArrayNotHasKey('email_verified', $claims); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + 'allow_implicit' => true, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $memoryStorage->supportedScopes[] = 'email'; + $responseTypes = array( + 'code' => $code = new AuthorizationCode($memoryStorage), + 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config), + 'code id_token' => new CodeIdToken($code, $idToken), + ); + + $server = new Server($memoryStorage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php new file mode 100644 index 00000000..a0df3a93 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php @@ -0,0 +1,185 @@ + 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + ); + + // attempt to do the request without a nonce. + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request($query); + $valid = $server->validateAuthorizeRequest($request, $response = new Response()); + + // Add a nonce and retry. + $query['nonce'] = 'test'; + $request = new Request($query); + $valid = $server->validateAuthorizeRequest($request, $response = new Response()); + $this->assertTrue($valid); + } + + public function testHandleAuthorizeRequest() + { + // add the test parameters in memory + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'response_type' => 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + $user_id = 'testuser'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $user_id); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayNotHasKey('access_token', $params); + $this->validateIdToken($params['id_token']); + } + + public function testPassInAuthTime() + { + $server = $this->getTestServer(array('allow_implicit' => true)); + $request = new Request(array( + 'response_type' => 'id_token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid email', + 'state' => 'test', + 'nonce' => 'test', + )); + + // test with a scalar user id + $user_id = 'testuser123'; + $server->handleAuthorizeRequest($request, $response = new Response(), true, $user_id); + + list($header, $payload, $signature) = $this->extractTokenDataFromResponse($response); + + $this->assertTrue(is_array($payload)); + $this->assertArrayHasKey('sub', $payload); + $this->assertEquals($user_id, $payload['sub']); + $this->assertArrayHasKey('auth_time', $payload); + + // test with an array of user info + $userInfo = array( + 'user_id' => 'testuser1234', + 'auth_time' => date('Y-m-d H:i:s', strtotime('20 minutes ago') + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true, $userInfo); + + list($header, $payload, $signature) = $this->extractTokenDataFromResponse($response); + + $this->assertTrue(is_array($payload)); + $this->assertArrayHasKey('sub', $payload); + $this->assertEquals($userInfo['user_id'], $payload['sub']); + $this->assertArrayHasKey('auth_time', $payload); + $this->assertEquals($userInfo['auth_time'], $payload['auth_time']); + } + + private function extractTokenDataFromResponse(Response $response) + { + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayNotHasKey('access_token', $params); + + list($headb64, $payloadb64, $signature) = explode('.', $params['id_token']); + + $jwt = new Jwt(); + $header = json_decode($jwt->urlSafeB64Decode($headb64), true); + $payload = json_decode($jwt->urlSafeB64Decode($payloadb64), true); + + return array($header, $payload, $signature); + } + + private function validateIdToken($id_token) + { + $parts = explode('.', $id_token); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + $this->assertArrayHasKey('email', $claims); + $this->assertArrayHasKey('email_verified', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $this->assertEquals($claims['email'], 'testuser@test.com'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $memoryStorage->supportedScopes[] = 'email'; + $storage = array( + 'client' => $memoryStorage, + 'scope' => $memoryStorage, + ); + $responseTypes = array( + 'id_token' => new IdToken($memoryStorage, $memoryStorage, $config), + ); + + $server = new Server($storage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php new file mode 100644 index 00000000..0573a986 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php @@ -0,0 +1,92 @@ +getTestServer(array('allow_implicit' => true)); + + $request = new Request(array( + 'response_type' => 'id_token token', + 'redirect_uri' => 'http://adobe.com', + 'client_id' => 'Test Client ID', + 'scope' => 'openid', + 'state' => 'test', + 'nonce' => 'test', + )); + + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + $this->assertEquals($response->getStatusCode(), 302); + $location = $response->getHttpHeader('Location'); + $this->assertNotContains('error', $location); + + $parts = parse_url($location); + $this->assertArrayHasKey('fragment', $parts); + $this->assertFalse(isset($parts['query'])); + + // assert fragment is in "application/x-www-form-urlencoded" format + parse_str($parts['fragment'], $params); + $this->assertNotNull($params); + $this->assertArrayHasKey('id_token', $params); + $this->assertArrayHasKey('access_token', $params); + + // validate ID Token + $parts = explode('.', $params['id_token']); + foreach ($parts as &$part) { + // Each part is a base64url encoded json string. + $part = str_replace(array('-', '_'), array('+', '/'), $part); + $part = base64_decode($part); + $part = json_decode($part, true); + } + list($header, $claims, $signature) = $parts; + + $this->assertArrayHasKey('iss', $claims); + $this->assertArrayHasKey('sub', $claims); + $this->assertArrayHasKey('aud', $claims); + $this->assertArrayHasKey('iat', $claims); + $this->assertArrayHasKey('exp', $claims); + $this->assertArrayHasKey('auth_time', $claims); + $this->assertArrayHasKey('nonce', $claims); + $this->assertArrayHasKey('at_hash', $claims); + + $this->assertEquals($claims['iss'], 'test'); + $this->assertEquals($claims['aud'], 'Test Client ID'); + $this->assertEquals($claims['nonce'], 'test'); + $duration = $claims['exp'] - $claims['iat']; + $this->assertEquals($duration, 3600); + } + + private function getTestServer($config = array()) + { + $config += array( + 'use_openid_connect' => true, + 'issuer' => 'test', + 'id_lifetime' => 3600, + ); + + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $responseTypes = array( + 'token' => $token = new AccessToken($memoryStorage, $memoryStorage), + 'id_token' => $idToken = new IdToken($memoryStorage, $memoryStorage, $config), + 'id_token token' => new IdTokenToken($token, $idToken), + ); + + $server = new Server($memoryStorage, $config, array(), $responseTypes); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php new file mode 100644 index 00000000..bdfb085e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/AuthorizationCodeTest.php @@ -0,0 +1,95 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof AuthorizationCodeInterface) { + return; + } + + // assert code we are about to add does not exist + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertFalse($code); + + // add new code + $expires = time() + 20; + $scope = null; + $id_token = 'fake_id_token'; + $success = $storage->setAuthorizationCode('new-openid-code', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'new-openid-code'); + $this->assertEquals($code['client_id'], 'client ID'); + $this->assertEquals($code['user_id'], 'SOMEUSERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.com'); + $this->assertEquals($code['expires'], $expires); + $this->assertEquals($code['id_token'], $id_token); + + // change existing code + $expires = time() + 42; + $new_id_token = 'fake_id_token-2'; + $success = $storage->setAuthorizationCode('new-openid-code', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires, $scope, $new_id_token); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('new-openid-code'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'new-openid-code'); + $this->assertEquals($code['client_id'], 'client ID2'); + $this->assertEquals($code['user_id'], 'SOMEOTHERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.org'); + $this->assertEquals($code['expires'], $expires); + $this->assertEquals($code['id_token'], $new_id_token); + } + + /** @dataProvider provideStorage */ + public function testRemoveIdTokenFromAuthorizationCode($storage) + { + // add new code + $expires = time() + 20; + $scope = null; + $id_token = 'fake_id_token_to_remove'; + $authcode = 'new-openid-code-'.rand(); + $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, $id_token); + $this->assertTrue($success); + + // verify params were set + $code = $storage->getAuthorizationCode($authcode); + $this->assertNotNull($code); + $this->assertArrayHasKey('id_token', $code); + $this->assertEquals($code['id_token'], $id_token); + + // remove the id_token + $success = $storage->setAuthorizationCode($authcode, 'client ID', 'SOMEUSERID', 'http://example.com', $expires, $scope, null); + + // verify the "id_token" is now null + $code = $storage->getAuthorizationCode($authcode); + $this->assertNotNull($code); + $this->assertArrayHasKey('id_token', $code); + $this->assertEquals($code['id_token'], null); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php new file mode 100644 index 00000000..840f6c56 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/Storage/UserClaimsTest.php @@ -0,0 +1,41 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof UserClaimsInterface) { + // incompatible storage + return; + } + + // invalid user + $claims = $storage->getUserClaims('fake-user', ''); + $this->assertFalse($claims); + + // valid user (no scope) + $claims = $storage->getUserClaims('testuser', ''); + + /* assert the decoded token is the same */ + $this->assertFalse(isset($claims['email'])); + + // valid user + $claims = $storage->getUserClaims('testuser', 'email'); + + /* assert the decoded token is the same */ + $this->assertEquals($claims['email'], "testuser@test.com"); + $this->assertEquals($claims['email_verified'], true); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php new file mode 100644 index 00000000..770cd899 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/RequestTest.php @@ -0,0 +1,117 @@ +getTestServer(); + + // Smoke test for override request class + // $server->handleTokenRequest($request, $response = new Response()); + // $this->assertInstanceOf('Response', $response); + // $server->handleAuthorizeRequest($request, $response = new Response(), true); + // $this->assertInstanceOf('Response', $response); + // $response = $server->verifyResourceRequest($request, $response = new Response()); + // $this->assertTrue(is_bool($response)); + + /*** make some valid requests ***/ + + // Valid Token Request + $request->setPost(array( + 'grant_type' => 'authorization_code', + 'client_id' => 'Test Client ID', + 'client_secret' => 'TestSecret', + 'code' => 'testcode', + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertEquals($response->getStatusCode(), 200); + $this->assertNull($response->getParameter('error')); + $this->assertNotNUll($response->getParameter('access_token')); + } + + public function testHeadersReturnsValueByKey() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array('AUTHORIZATION' => 'Basic secret') + ); + + $this->assertEquals('Basic secret', $request->headers('AUTHORIZATION')); + } + + public function testHeadersReturnsDefaultIfHeaderNotPresent() + { + $request = new Request(); + + $this->assertEquals('Bearer', $request->headers('AUTHORIZATION', 'Bearer')); + } + + public function testHeadersIsCaseInsensitive() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array(), + array(), + array('AUTHORIZATION' => 'Basic secret') + ); + + $this->assertEquals('Basic secret', $request->headers('Authorization')); + } + + public function testRequestReturnsPostParamIfNoQueryParamAvailable() + { + $request = new Request( + array(), + array('client_id' => 'correct') + ); + + $this->assertEquals('correct', $request->query('client_id', $request->request('client_id'))); + } + + public function testRequestHasHeadersAndServerHeaders() + { + $request = new Request( + array(), + array(), + array(), + array(), + array(), + array('CONTENT_TYPE' => 'text/xml', 'PHP_AUTH_USER' => 'client_id', 'PHP_AUTH_PW' => 'client_pass'), + null, + array('CONTENT_TYPE' => 'application/json') + ); + + $this->assertSame('client_id', $request->headers('PHP_AUTH_USER')); + $this->assertSame('client_pass', $request->headers('PHP_AUTH_PW')); + $this->assertSame('application/json', $request->headers('CONTENT_TYPE')); + } + + private function getTestServer($config = array()) + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, $config); + + // Add the two types supported for authorization grant + $server->addGrantType(new AuthorizationCode($storage)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php new file mode 100644 index 00000000..172bc88f --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php @@ -0,0 +1,38 @@ + 'bar', + 'halland' => 'oates', + )); + + $string = $response->getResponseBody('xml'); + $this->assertContains('baroates', $string); + } + + public function testSetRedirect() + { + $response = new Response(); + $url = 'https://foo/bar'; + $state = 'stateparam'; + $response->setRedirect(301, $url, $state); + $this->assertEquals( + sprintf('%s?state=%s', $url, $state), + $response->getHttpHeader('Location') + ); + + $query = 'query=foo'; + $response->setRedirect(301, $url . '?' . $query, $state); + $this->assertEquals( + sprintf('%s?%s&state=%s', $url, $query, $state), + $response->getHttpHeader('Location') + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php new file mode 100644 index 00000000..43b02254 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/AccessTokenTest.php @@ -0,0 +1,108 @@ + array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'access_token'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeAccessTokenWithoutTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeRefreshTokenWithTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke', 'refresh_token'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } + + public function testRevokeRefreshTokenWithoutTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } + + public function testRevokeAccessTokenWithRefreshTokenTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'refresh_token'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeAccessTokenWithBogusTypeHint() + { + $tokenStorage = new Memory(array( + 'access_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getAccessToken('revoke')); + $accessToken = new AccessToken($tokenStorage); + $accessToken->revokeToken('revoke', 'foo'); + $this->assertFalse($tokenStorage->getAccessToken('revoke')); + } + + public function testRevokeRefreshTokenWithBogusTypeHint() + { + $tokenStorage = new Memory(array( + 'refresh_tokens' => array( + 'revoke' => array('mytoken'), + ), + )); + + $this->assertEquals(array('mytoken'), $tokenStorage->getRefreshToken('revoke')); + $accessToken = new AccessToken(new Memory, $tokenStorage); + $accessToken->revokeToken('revoke', 'foo'); + $this->assertFalse($tokenStorage->getRefreshToken('revoke')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php new file mode 100644 index 00000000..6195d557 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php @@ -0,0 +1,178 @@ +getTestServer(); + $jwtResponseType = $server->getResponseType('token'); + + $accessToken = $jwtResponseType->createAccessToken('Test Client ID', 123, 'test', false); + $jwt = new Jwt; + $decodedAccessToken = $jwt->decode($accessToken['access_token'], null, false); + + $this->assertArrayHasKey('id', $decodedAccessToken); + $this->assertArrayHasKey('jti', $decodedAccessToken); + $this->assertArrayHasKey('iss', $decodedAccessToken); + $this->assertArrayHasKey('aud', $decodedAccessToken); + $this->assertArrayHasKey('exp', $decodedAccessToken); + $this->assertArrayHasKey('iat', $decodedAccessToken); + $this->assertArrayHasKey('token_type', $decodedAccessToken); + $this->assertArrayHasKey('scope', $decodedAccessToken); + + $this->assertEquals('https://api.example.com', $decodedAccessToken['iss']); + $this->assertEquals('Test Client ID', $decodedAccessToken['aud']); + $this->assertEquals(123, $decodedAccessToken['sub']); + $delta = $decodedAccessToken['exp'] - $decodedAccessToken['iat']; + $this->assertEquals(3600, $delta); + $this->assertEquals($decodedAccessToken['id'], $decodedAccessToken['jti']); + } + + public function testExtraPayloadCallback() + { + $jwtconfig = array('jwt_extra_payload_callable' => function() { + return array('custom_param' => 'custom_value'); + }); + + $server = $this->getTestServer($jwtconfig); + $jwtResponseType = $server->getResponseType('token'); + + $accessToken = $jwtResponseType->createAccessToken('Test Client ID', 123, 'test', false); + $jwt = new Jwt; + $decodedAccessToken = $jwt->decode($accessToken['access_token'], null, false); + + $this->assertArrayHasKey('custom_param', $decodedAccessToken); + $this->assertEquals('custom_value', $decodedAccessToken['custom_param']); + } + + public function testGrantJwtAccessToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + + $this->assertNotNull($response->getParameter('access_token')); + $this->assertEquals(2, substr_count($response->getParameter('access_token'), '.')); + } + + public function testAccessResourceWithJwtAccessToken() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $JwtAccessToken, + )); + + $this->assertTrue($server->verifyResourceRequest($request)); + } + + public function testAccessResourceWithJwtAccessTokenUsingSecondaryStorage() + { + // add the test parameters in memory + $server = $this->getTestServer(); + $request = TestRequest::createPost(array( + 'grant_type' => 'client_credentials', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + )); + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + + // make a call to the resource server using the crypto token + $request = TestRequest::createPost(array( + 'access_token' => $JwtAccessToken, + )); + + // create a resource server with the "memory" storage from the grant server + $resourceServer = new Server($server->getStorage('client_credentials')); + + $this->assertTrue($resourceServer->verifyResourceRequest($request)); + } + + public function testJwtAccessTokenWithRefreshToken() + { + $server = $this->getTestServer(); + + // add "UserCredentials" grant type and "JwtAccessToken" response type + // and ensure "JwtAccessToken" response type has "RefreshToken" storage + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + $server->addGrantType(new UserCredentials($memoryStorage)); + $server->addGrantType(new RefreshToken($memoryStorage)); + $server->addResponseType(new JwtAccessToken($memoryStorage, $memoryStorage, $memoryStorage), 'token'); + + $request = TestRequest::createPost(array( + 'grant_type' => 'password', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'username' => 'test-username', // valid username + 'password' => 'testpass', // valid password + )); + + // make the call to grant a crypto token + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($JwtAccessToken = $response->getParameter('access_token')); + $this->assertNotNull($refreshToken = $response->getParameter('refresh_token')); + + // decode token and make sure refresh_token isn't set + list($header, $payload, $signature) = explode('.', $JwtAccessToken); + $decodedToken = json_decode(base64_decode($payload), true); + $this->assertFalse(array_key_exists('refresh_token', $decodedToken)); + + // use the refresh token to get another access token + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => $refreshToken, + )); + + $server->handleTokenRequest($request, $response = new Response()); + $this->assertNotNull($response->getParameter('access_token')); + } + + private function getTestServer($jwtconfig = array()) + { + $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); + + $storage = array( + 'access_token' => new JwtAccessTokenStorage($memoryStorage), + 'client' => $memoryStorage, + 'client_credentials' => $memoryStorage, + ); + $server = new Server($storage); + $server->addGrantType(new ClientCredentials($memoryStorage)); + + // make the "token" response type a JwtAccessToken + $config = array_merge(array('issuer' => 'https://api.example.com'), $jwtconfig); + $server->addResponseType(new JwtAccessToken($memoryStorage, $memoryStorage, null, $config)); + + return $server; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php new file mode 100644 index 00000000..5b51be1d --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ScopeTest.php @@ -0,0 +1,43 @@ +assertFalse($scopeUtil->checkScope('invalid', 'list of scopes')); + $this->assertTrue($scopeUtil->checkScope('valid', 'valid and-some other-scopes')); + $this->assertTrue($scopeUtil->checkScope('valid another-valid', 'valid another-valid and-some other-scopes')); + // all scopes must match + $this->assertFalse($scopeUtil->checkScope('valid invalid', 'valid and-some other-scopes')); + $this->assertFalse($scopeUtil->checkScope('valid valid2 invalid', 'valid valid2 and-some other-scopes')); + } + + public function testScopeStorage() + { + $scopeUtil = new Scope(); + $this->assertEquals($scopeUtil->getDefaultScope(), null); + + $scopeUtil = new Scope(array( + 'default_scope' => 'default', + 'supported_scopes' => array('this', 'that', 'another'), + )); + $this->assertEquals($scopeUtil->getDefaultScope(), 'default'); + $this->assertTrue($scopeUtil->scopeExists('this that another', 'client_id')); + + $memoryStorage = new Memory(array( + 'default_scope' => 'base', + 'supported_scopes' => array('only-this-one'), + )); + $scopeUtil = new Scope($memoryStorage); + + $this->assertEquals($scopeUtil->getDefaultScope(), 'base'); + $this->assertTrue($scopeUtil->scopeExists('only-this-one', 'client_id')); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php new file mode 100644 index 00000000..3106961e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php @@ -0,0 +1,685 @@ +getAuthorizeController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetAuthorizeControllerWithNoAccessTokenStorageThrowsException() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->getAuthorizeController(); + } + + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenResponseType() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->getMock('OAuth2\ResponseType\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeResponseType() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->getMock('OAuth2\ResponseType\AuthorizationCodeInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException allow_implicit + **/ + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorageThrowsException() + { + // must set AuthorizationCode or AccessToken / implicit + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorage() + { + // must set AuthorizationCode or AccessToken / implicit + $server = new Server(array(), array('allow_implicit' => true)); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + public function testGetAuthorizeControllerWithClientStorageAndAuthorizationCodeStorage() + { + // must set AccessToken or AuthorizationCode + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')); + + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException grant_types + **/ + public function testGetTokenControllerWithGrantTypeStorageThrowsException() + { + $server = new Server(); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\ClientCredentialsInterface + **/ + public function testGetTokenControllerWithNoClientCredentialsStorageThrowsException() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\UserCredentialsInterface')); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetTokenControllerWithNoAccessTokenStorageThrowsException() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->getTokenController(); + } + + public function testGetTokenControllerWithAccessTokenAndClientCredentialsStorage() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->getTokenController(); + } + + public function testGetTokenControllerAccessTokenStorageAndClientCredentialsStorageAndGrantTypes() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addGrantType($this->getMockBuilder('OAuth2\GrantType\AuthorizationCode')->disableOriginalConstructor()->getMock()); + $server->getTokenController(); + } + + /** + * @expectedException LogicException OAuth2\Storage\AccessTokenInterface + **/ + public function testGetResourceControllerWithNoAccessTokenStorageThrowsException() + { + $server = new Server(); + $server->getResourceController(); + } + + public function testGetResourceControllerWithAccessTokenStorage() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->getResourceController(); + } + + /** + * @expectedException InvalidArgumentException OAuth2\Storage\AccessTokenInterface + **/ + public function testAddingStorageWithInvalidClass() + { + $server = new Server(); + $server->addStorage(new \StdClass()); + } + + /** + * @expectedException InvalidArgumentException access_token + **/ + public function testAddingStorageWithInvalidKey() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'nonexistant_storage'); + } + + /** + * @expectedException InvalidArgumentException OAuth2\Storage\AuthorizationCodeInterface + **/ + public function testAddingStorageWithInvalidKeyStorageCombination() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'authorization_code'); + } + + public function testAddingStorageWithValidKeyOnlySetsThatKey() + { + $server = new Server(); + $server->addStorage($this->getMock('OAuth2\Storage\Memory'), 'access_token'); + + $reflection = new \ReflectionClass($server); + $prop = $reflection->getProperty('storages'); + $prop->setAccessible(true); + + $storages = $prop->getValue($server); // get the private "storages" property + + $this->assertEquals(1, count($storages)); + $this->assertTrue(isset($storages['access_token'])); + $this->assertFalse(isset($storages['authorization_code'])); + } + + public function testAddingClientStorageSetsClientCredentialsStorageByDefault() + { + $server = new Server(); + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server->addStorage($memory, 'client'); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + } + + public function testAddStorageWithNullValue() + { + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server = new Server($memory); + $server->addStorage(null, 'refresh_token'); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + + $refresh_token = $server->getStorage('refresh_token'); + + $this->assertNull($refresh_token); + } + + public function testNewServerWithNullStorageValue() + { + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server = new Server(array( + 'client_credentials' => $memory, + 'refresh_token' => null, + )); + + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertNotNull($client_credentials); + $this->assertEquals($client_credentials, $memory); + + $refresh_token = $server->getStorage('refresh_token'); + + $this->assertNull($refresh_token); + } + + public function testAddingClientCredentialsStorageSetsClientStorageByDefault() + { + $server = new Server(); + $memory = $this->getMock('OAuth2\Storage\Memory'); + $server->addStorage($memory, 'client_credentials'); + + $client = $server->getStorage('client'); + + $this->assertNotNull($client); + $this->assertEquals($client, $memory); + } + + public function testSettingClientStorageByDefaultDoesNotOverrideSetStorage() + { + $server = new Server(); + $pdo = $this->getMockBuilder('OAuth2\Storage\Pdo') + ->disableOriginalConstructor()->getMock(); + + $memory = $this->getMock('OAuth2\Storage\Memory'); + + $server->addStorage($pdo, 'client'); + $server->addStorage($memory, 'client_credentials'); + + $client = $server->getStorage('client'); + $client_credentials = $server->getStorage('client_credentials'); + + $this->assertEquals($client, $pdo); + $this->assertEquals($client_credentials, $memory); + } + + public function testAddingResponseType() + { + $storage = $this->getMock('OAuth2\Storage\Memory'); + $storage + ->expects($this->any()) + ->method('getClientDetails') + ->will($this->returnValue(array('client_id' => 'some_client'))); + $storage + ->expects($this->any()) + ->method('checkRestrictedGrantType') + ->will($this->returnValue(true)); + + // add with the "code" key explicitly set + $codeType = new AuthorizationCode($storage); + $server = new Server(); + $server->addStorage($storage); + $server->addResponseType($codeType); + $request = new Request(array( + 'response_type' => 'code', + 'client_id' => 'some_client', + 'redirect_uri' => 'http://example.com', + 'state' => 'xyx', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + // the response is successful + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + $this->assertTrue(isset($query['code'])); + $this->assertFalse(isset($query['error'])); + + // add with the "code" key not set + $codeType = new AuthorizationCode($storage); + $server = new Server(array($storage), array(), array(), array($codeType)); + $request = new Request(array( + 'response_type' => 'code', + 'client_id' => 'some_client', + 'redirect_uri' => 'http://example.com', + 'state' => 'xyx', + )); + $server->handleAuthorizeRequest($request, $response = new Response(), true); + + // the response is successful + $this->assertEquals($response->getStatusCode(), 302); + $parts = parse_url($response->getHttpHeader('Location')); + parse_str($parts['query'], $query); + $this->assertTrue(isset($query['code'])); + $this->assertFalse(isset($query['error'])); + } + + public function testCustomClientAssertionType() + { + $request = TestRequest::createPost(array( + 'grant_type' => 'authorization_code', + 'client_id' =>'Test Client ID', + 'code' => 'testcode', + )); + // verify the mock clientAssertionType was called as expected + $clientAssertionType = $this->getMock('OAuth2\ClientAssertionType\ClientAssertionTypeInterface', array('validateRequest', 'getClientId')); + $clientAssertionType + ->expects($this->once()) + ->method('validateRequest') + ->will($this->returnValue(true)); + $clientAssertionType + ->expects($this->once()) + ->method('getClientId') + ->will($this->returnValue('Test Client ID')); + + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server(array($storage), array(), array(), array(), null, null, $clientAssertionType); + $server->handleTokenRequest($request, $response = new Response()); + } + + public function testHttpBasicConfig() + { + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server(array($storage), array( + 'allow_credentials_in_request_body' => false, + 'allow_public_clients' => false + )); + $server->getTokenController(); + $httpBasic = $server->getClientAssertionType(); + + $reflection = new \ReflectionClass($httpBasic); + $prop = $reflection->getProperty('config'); + $prop->setAccessible(true); + + $config = $prop->getValue($httpBasic); // get the private "config" property + + $this->assertEquals($config['allow_credentials_in_request_body'], false); + $this->assertEquals($config['allow_public_clients'], false); + } + + public function testRefreshTokenConfig() + { + // create mock storage + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server1 = new Server(array($storage)); + $server2 = new Server(array($storage), array('always_issue_new_refresh_token' => true, 'unset_refresh_token_after_use' => false)); + + $server1->getTokenController(); + $refreshToken1 = $server1->getGrantType('refresh_token'); + + $server2->getTokenController(); + $refreshToken2 = $server2->getGrantType('refresh_token'); + + $reflection1 = new \ReflectionClass($refreshToken1); + $prop1 = $reflection1->getProperty('config'); + $prop1->setAccessible(true); + + $reflection2 = new \ReflectionClass($refreshToken2); + $prop2 = $reflection2->getProperty('config'); + $prop2->setAccessible(true); + + // get the private "config" property + $config1 = $prop1->getValue($refreshToken1); + $config2 = $prop2->getValue($refreshToken2); + + $this->assertEquals($config1['always_issue_new_refresh_token'], false); + $this->assertEquals($config2['always_issue_new_refresh_token'], true); + + $this->assertEquals($config1['unset_refresh_token_after_use'], true); + $this->assertEquals($config2['unset_refresh_token_after_use'], false); + } + + /** + * Test setting "always_issue_new_refresh_token" on a server level + * + * @see test/OAuth2/GrantType/RefreshTokenTest::testValidRefreshTokenWithNewRefreshTokenInResponse + **/ + public function testValidRefreshTokenWithNewRefreshTokenInResponse() + { + $storage = Bootstrap::getInstance()->getMemoryStorage(); + $server = new Server($storage, array('always_issue_new_refresh_token' => true)); + + $request = TestRequest::createPost(array( + 'grant_type' => 'refresh_token', // valid grant type + 'client_id' => 'Test Client ID', // valid client id + 'client_secret' => 'TestSecret', // valid client secret + 'refresh_token' => 'test-refreshtoken', // valid refresh token + )); + $token = $server->grantAccessToken($request, new Response()); + $this->assertTrue(isset($token['refresh_token']), 'refresh token should always refresh'); + + $refresh_token = $storage->getRefreshToken($token['refresh_token']); + $this->assertNotNull($refresh_token); + $this->assertEquals($refresh_token['refresh_token'], $token['refresh_token']); + $this->assertEquals($refresh_token['client_id'], $request->request('client_id')); + $this->assertTrue($token['refresh_token'] != 'test-refreshtoken', 'the refresh token returned is not the one used'); + $used_token = $storage->getRefreshToken('test-refreshtoken'); + $this->assertFalse($used_token, 'the refresh token used is no longer valid'); + } + + /** + * @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface + **/ + public function testAddingUnknownResponseTypeThrowsException() + { + $server = new Server(); + $server->addResponseType($this->getMock('OAuth2\ResponseType\ResponseTypeInterface')); + } + + /** + * @expectedException LogicException OAuth2\Storage\PublicKeyInterface + **/ + public function testUsingJwtAccessTokensWithoutPublicKeyStorageThrowsException() + { + $server = new Server(array(), array('use_jwt_access_tokens' => true)); + $server->addGrantType($this->getMock('OAuth2\GrantType\GrantTypeInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + + $server->getTokenController(); + } + + public function testUsingJustJwtAccessTokenStorageWithResourceControllerIsOkay() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + + $this->assertNotNull($server->getResourceController()); + $this->assertInstanceOf('OAuth2\Storage\PublicKeyInterface', $server->getStorage('public_key')); + } + + /** + * @expectedException LogicException OAuth2\Storage\ClientInterface + **/ + public function testUsingJustJwtAccessTokenStorageWithAuthorizeControllerThrowsException() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + $this->assertNotNull($server->getAuthorizeController()); + } + + /** + * @expectedException LogicException grant_types + **/ + public function testUsingJustJwtAccessTokenStorageWithTokenControllerThrowsException() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); + $server->getTokenController(); + } + + public function testUsingJwtAccessTokenAndClientStorageWithAuthorizeControllerIsOkay() + { + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $server = new Server(array($pubkey, $client), array('use_jwt_access_tokens' => true, 'allow_implicit' => true)); + $this->assertNotNull($server->getAuthorizeController()); + + $this->assertInstanceOf('OAuth2\ResponseType\JwtAccessToken', $server->getResponseType('token')); + } + + /** + * @expectedException LogicException UserClaims + **/ + public function testUsingOpenIDConnectWithoutUserClaimsThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $server = new Server($client, array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + /** + * @expectedException LogicException PublicKeyInterface + **/ + public function testUsingOpenIDConnectWithoutPublicKeyThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OPenID\Storage\UserClaimsInterface'); + $server = new Server(array($client, $userclaims), array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + /** + * @expectedException LogicException issuer + **/ + public function testUsingOpenIDConnectWithoutIssuerThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array('use_openid_connect' => true)); + + $server->getAuthorizeController(); + } + + public function testUsingOpenIDConnectWithIssuerPublicKeyAndUserClaimsIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertNull($server->getResponseType('id_token token')); + } + + /** + * @expectedException LogicException OAuth2\ResponseType\AccessTokenInterface + **/ + public function testUsingOpenIDConnectWithAllowImplicitWithoutTokenStorageThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $server->getAuthorizeController(); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndUseJwtAccessTokensIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + 'use_jwt_access_tokens' => true, + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenStorageIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $server = new Server(array($client, $userclaims, $pubkey, $token), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenResponseTypeIsOkay() + { + $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + // $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $server = new Server(array($client, $userclaims, $pubkey), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy', + 'allow_implicit' => true, + )); + + $token = $this->getMock('OAuth2\ResponseType\AccessTokenInterface'); + $server->addResponseType($token, 'token'); + + $server->getAuthorizeController(); + + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenInterface', $server->getResponseType('id_token')); + $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); + } + + /** + * @expectedException LogicException OAuth2\OpenID\Storage\AuthorizationCodeInterface + **/ + public function testUsingOpenIDConnectWithAuthorizationCodeStorageThrowsException() + { + $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->getMock('OAuth2\Storage\AuthorizationCodeInterface'); + + $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy' + )); + + $server->getTokenController(); + + $this->assertInstanceOf('OAuth2\OpenID\GrantType\AuthorizationCode', $server->getGrantType('authorization_code')); + } + + public function testUsingOpenIDConnectWithOpenIDAuthorizationCodeStorageCreatesOpenIDAuthorizationCodeGrantType() + { + $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->getMock('OAuth2\OpenID\Storage\AuthorizationCodeInterface'); + + $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( + 'use_openid_connect' => true, + 'issuer' => 'someguy' + )); + + $server->getTokenController(); + + $this->assertInstanceOf('OAuth2\OpenID\GrantType\AuthorizationCode', $server->getGrantType('authorization_code')); + } + + public function testMultipleValuedResponseTypeOrderDoesntMatter() + { + $responseType = $this->getMock('OAuth2\OpenID\ResponseType\IdTokenTokenInterface'); + $server = new Server(array(), array(), array(), array( + 'token id_token' => $responseType, + )); + + $this->assertEquals($responseType, $server->getResponseType('id_token token')); + } + + public function testAddGrantTypeWithoutKey() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface'))); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('authorization_code', key($grantTypes)); + } + + public function testAddGrantTypeWithKey() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 'ac'); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('ac', key($grantTypes)); + } + + public function testAddGrantTypeWithKeyNotString() + { + $server = new Server(); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 42); + + $grantTypes = $server->getGrantTypes(); + $this->assertEquals('authorization_code', key($grantTypes)); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php new file mode 100644 index 00000000..b34e0bfc --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AccessTokenTest.php @@ -0,0 +1,102 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to add does not exist + $token = $storage->getAccessToken('newtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + + // change existing token + $expires = time() + 42; + $success = $storage->setAccessToken('newtoken', 'client ID2', 'SOMEOTHERID', $expires); + $this->assertTrue($success); + + $token = $storage->getAccessToken('newtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('access_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['access_token'], 'newtoken'); + $this->assertEquals($token['client_id'], 'client ID2'); + $this->assertEquals($token['user_id'], 'SOMEOTHERID'); + $this->assertEquals($token['expires'], $expires); + + // add token with scope having an empty string value + $expires = time() + 42; + $success = $storage->setAccessToken('newtoken', 'client ID', 'SOMEOTHERID', $expires, ''); + $this->assertTrue($success); + } + + /** @dataProvider provideStorage */ + public function testUnsetAccessToken(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to unset does not exist + $token = $storage->getAccessToken('revokabletoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setAccessToken('revokabletoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + // assert unsetAccessToken returns true + $result = $storage->unsetAccessToken('revokabletoken'); + $this->assertTrue($result); + + // assert token we unset does not exist + $token = $storage->getAccessToken('revokabletoken'); + $this->assertFalse($token); + } + + /** @dataProvider provideStorage */ + public function testUnsetAccessTokenReturnsFalse(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage || !method_exists($storage, 'unsetAccessToken')) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to unset does not exist + $token = $storage->getAccessToken('nonexistanttoken'); + $this->assertFalse($token); + + // assert unsetAccessToken returns false + $result = $storage->unsetAccessToken('nonexistanttoken'); + $this->assertFalse($result); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php new file mode 100644 index 00000000..2d901b50 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/AuthorizationCodeTest.php @@ -0,0 +1,106 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getAuthorizationCode('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAuthorizationCode('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testSetAuthorizationCode(AuthorizationCodeInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert code we are about to add does not exist + $code = $storage->getAuthorizationCode('newcode'); + $this->assertFalse($code); + + // add new code + $expires = time() + 20; + $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID'); + $this->assertEquals($code['user_id'], 'SOMEUSERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.com'); + $this->assertEquals($code['expires'], $expires); + + // change existing code + $expires = time() + 42; + $success = $storage->setAuthorizationCode('newcode', 'client ID2', 'SOMEOTHERID', 'http://example.org', $expires); + $this->assertTrue($success); + + $code = $storage->getAuthorizationCode('newcode'); + $this->assertNotNull($code); + $this->assertArrayHasKey('authorization_code', $code); + $this->assertArrayHasKey('client_id', $code); + $this->assertArrayHasKey('user_id', $code); + $this->assertArrayHasKey('redirect_uri', $code); + $this->assertArrayHasKey('expires', $code); + $this->assertEquals($code['authorization_code'], 'newcode'); + $this->assertEquals($code['client_id'], 'client ID2'); + $this->assertEquals($code['user_id'], 'SOMEOTHERID'); + $this->assertEquals($code['redirect_uri'], 'http://example.org'); + $this->assertEquals($code['expires'], $expires); + + // add new code with scope having an empty string value + $expires = time() + 20; + $success = $storage->setAuthorizationCode('newcode', 'client ID', 'SOMEUSERID', 'http://example.com', $expires, ''); + $this->assertTrue($success); + } + + /** @dataProvider provideStorage */ + public function testExpireAccessToken(AccessTokenInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // create a valid code + $expires = time() + 20; + $success = $storage->setAuthorizationCode('code-to-expire', 'client ID', 'SOMEUSERID', 'http://example.com', time() + 20); + $this->assertTrue($success); + + // verify the new code exists + $code = $storage->getAuthorizationCode('code-to-expire'); + $this->assertNotNull($code); + + $this->assertArrayHasKey('authorization_code', $code); + $this->assertEquals($code['authorization_code'], 'code-to-expire'); + + // now expire the code and ensure it's no longer available + $storage->expireAuthorizationCode('code-to-expire'); + $code = $storage->getAuthorizationCode('code-to-expire'); + $this->assertFalse($code); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php new file mode 100644 index 00000000..15289af3 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientCredentialsTest.php @@ -0,0 +1,28 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $pass = $storage->checkClientCredentials('fakeclient', 'testpass'); + $this->assertFalse($pass); + + // invalid password + $pass = $storage->checkClientCredentials('oauth_test_client', 'invalidcredentials'); + $this->assertFalse($pass); + + // valid credentials + $pass = $storage->checkClientCredentials('oauth_test_client', 'testpass'); + $this->assertTrue($pass); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php new file mode 100644 index 00000000..6a5cc0b4 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ClientTest.php @@ -0,0 +1,110 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getClientDetails('fakeclient'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getClientDetails('oauth_test_client'); + $this->assertNotNull($details); + $this->assertArrayHasKey('client_id', $details); + $this->assertArrayHasKey('client_secret', $details); + $this->assertArrayHasKey('redirect_uri', $details); + } + + /** @dataProvider provideStorage */ + public function testCheckRestrictedGrantType(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // Check invalid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'authorization_code'); + $this->assertFalse($pass); + + // Check valid + $pass = $storage->checkRestrictedGrantType('oauth_test_client', 'implicit'); + $this->assertTrue($pass); + } + + /** @dataProvider provideStorage */ + public function testGetAccessToken(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $details = $storage->getAccessToken('faketoken'); + $this->assertFalse($details); + + // valid client_id + $details = $storage->getAccessToken('testtoken'); + $this->assertNotNull($details); + } + + /** @dataProvider provideStorage */ + public function testIsPublicClient(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + $publicClientId = 'public-client-'.rand(); + $confidentialClientId = 'confidential-client-'.rand(); + + // create a new client + $success1 = $storage->setClientDetails($publicClientId, ''); + $success2 = $storage->setClientDetails($confidentialClientId, 'some-secret'); + $this->assertTrue($success1); + $this->assertTrue($success2); + + // assert isPublicClient for both + $this->assertTrue($storage->isPublicClient($publicClientId)); + $this->assertFalse($storage->isPublicClient($confidentialClientId)); + } + + /** @dataProvider provideStorage */ + public function testSaveClient(ClientInterface $storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + $clientId = 'some-client-'.rand(); + + // create a new client + $success = $storage->setClientDetails($clientId, 'somesecret', 'http://test.com', 'client_credentials', 'clientscope1', 'brent@brentertainment.com'); + $this->assertTrue($success); + + // valid client_id + $details = $storage->getClientDetails($clientId); + $this->assertEquals($details['client_secret'], 'somesecret'); + $this->assertEquals($details['redirect_uri'], 'http://test.com'); + $this->assertEquals($details['grant_types'], 'client_credentials'); + $this->assertEquals($details['scope'], 'clientscope1'); + $this->assertEquals($details['user_id'], 'brent@brentertainment.com'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php new file mode 100644 index 00000000..2147f091 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/DynamoDBTest.php @@ -0,0 +1,40 @@ +getMockBuilder('\Aws\DynamoDb\DynamoDbClient') + ->disableOriginalConstructor() + ->setMethods(array('query')) + ->getMock(); + + $return = $this->getMockBuilder('\Guzzle\Service\Resource\Model') + ->setMethods(array('count', 'toArray')) + ->getMock(); + + $data = array( + 'Items' => array(), + 'Count' => 0, + 'ScannedCount'=> 0 + ); + + $return->expects($this->once()) + ->method('count') + ->will($this->returnValue(count($data))); + + $return->expects($this->once()) + ->method('toArray') + ->will($this->returnValue($data)); + + // should return null default scope if none is set in database + $client->expects($this->once()) + ->method('query') + ->will($this->returnValue($return)); + + $storage = new DynamoDB($client); + $this->assertNull($storage->getDefaultScope()); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php new file mode 100644 index 00000000..a6acbea1 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtAccessTokenTest.php @@ -0,0 +1,41 @@ +getMemoryStorage(); + $encryptionUtil = new Jwt(); + + $jwtAccessToken = array( + 'access_token' => rand(), + 'expires' => time() + 100, + 'scope' => 'foo', + ); + + $token = $encryptionUtil->encode($jwtAccessToken, $storage->getPrivateKey(), $storage->getEncryptionAlgorithm()); + + $this->assertNotNull($token); + + $tokenData = $crypto->getAccessToken($token); + + $this->assertTrue(is_array($tokenData)); + + /* assert the decoded token is the same */ + $this->assertEquals($tokenData['access_token'], $jwtAccessToken['access_token']); + $this->assertEquals($tokenData['expires'], $jwtAccessToken['expires']); + $this->assertEquals($tokenData['scope'], $jwtAccessToken['scope']); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php new file mode 100644 index 00000000..d0ab9b89 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/JwtBearerTest.php @@ -0,0 +1,25 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // nonexistant client_id + $key = $storage->getClientKey('this-is-not-real', 'nor-is-this'); + $this->assertFalse($key); + + // valid client_id and subject + $key = $storage->getClientKey('oauth_test_client', 'test_subject'); + $this->assertNotNull($key); + $this->assertEquals($key, Bootstrap::getInstance()->getTestPublicKey()); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php new file mode 100644 index 00000000..4599f69b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php @@ -0,0 +1,41 @@ +getSqliteDir()); + $pdo = new \PDO($dsn); + $storage = new Pdo($pdo); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + public function testCreatePdoStorageUsingDSN() + { + $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir()); + $storage = new Pdo($dsn); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + public function testCreatePdoStorageUsingConfig() + { + $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir()); + $config = array('dsn' => $dsn); + $storage = new Pdo($config); + + $this->assertNotNull($storage->getClientDetails('oauth_test_client')); + } + + /** + * @expectedException InvalidArgumentException dsn + */ + public function testCreatePdoStorageWithoutDSNThrowsException() + { + $config = array('username' => 'brent', 'password' => 'brentisaballer'); + $storage = new Pdo($config); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php new file mode 100644 index 00000000..f8519587 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PublicKeyTest.php @@ -0,0 +1,29 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof PublicKeyInterface) { + // incompatible storage + return; + } + + $configDir = Bootstrap::getInstance()->getConfigDir(); + $globalPublicKey = file_get_contents($configDir.'/keys/id_rsa.pub'); + $globalPrivateKey = file_get_contents($configDir.'/keys/id_rsa'); + + /* assert values from storage */ + $this->assertEquals($storage->getPublicKey(), $globalPublicKey); + $this->assertEquals($storage->getPrivateKey(), $globalPrivateKey); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php new file mode 100644 index 00000000..314c9319 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/RefreshTokenTest.php @@ -0,0 +1,41 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // assert token we are about to add does not exist + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertFalse($token); + + // add new token + $expires = time() + 20; + $success = $storage->setRefreshToken('refreshtoken', 'client ID', 'SOMEUSERID', $expires); + $this->assertTrue($success); + + $token = $storage->getRefreshToken('refreshtoken'); + $this->assertNotNull($token); + $this->assertArrayHasKey('refresh_token', $token); + $this->assertArrayHasKey('client_id', $token); + $this->assertArrayHasKey('user_id', $token); + $this->assertArrayHasKey('expires', $token); + $this->assertEquals($token['refresh_token'], 'refreshtoken'); + $this->assertEquals($token['client_id'], 'client ID'); + $this->assertEquals($token['user_id'], 'SOMEUSERID'); + $this->assertEquals($token['expires'], $expires); + + // add token with scope having an empty string value + $expires = time() + 20; + $success = $storage->setRefreshToken('refreshtoken2', 'client ID', 'SOMEUSERID', $expires, ''); + $this->assertTrue($success); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php new file mode 100644 index 00000000..fd1edeb9 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/ScopeTest.php @@ -0,0 +1,53 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof ScopeInterface) { + // incompatible storage + return; + } + + //Test getting scopes + $scopeUtil = new Scope($storage); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1')); + $this->assertTrue($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3')); + $this->assertFalse($scopeUtil->scopeExists('fakescope')); + $this->assertFalse($scopeUtil->scopeExists('supportedscope1 supportedscope2 supportedscope3 fakescope')); + } + + /** @dataProvider provideStorage */ + public function testGetDefaultScope($storage) + { + if ($storage instanceof NullStorage) { + $this->markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + if (!$storage instanceof ScopeInterface) { + // incompatible storage + return; + } + + // test getting default scope + $scopeUtil = new Scope($storage); + $expected = explode(' ', $scopeUtil->getDefaultScope()); + $actual = explode(' ', 'defaultscope1 defaultscope2'); + sort($expected); + sort($actual); + $this->assertEquals($expected, $actual); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php new file mode 100644 index 00000000..65655a6b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/UserCredentialsTest.php @@ -0,0 +1,40 @@ +markTestSkipped('Skipped Storage: ' . $storage->getMessage()); + + return; + } + + // create a new user for testing + $success = $storage->setUser('testusername', 'testpass', 'Test', 'User'); + $this->assertTrue($success); + + // correct credentials + $this->assertTrue($storage->checkUserCredentials('testusername', 'testpass')); + // invalid password + $this->assertFalse($storage->checkUserCredentials('testusername', 'fakepass')); + // invalid username + $this->assertFalse($storage->checkUserCredentials('fakeusername', 'testpass')); + + // invalid username + $this->assertFalse($storage->getUserDetails('fakeusername')); + + // ensure all properties are set + $user = $storage->getUserDetails('testusername'); + $this->assertTrue($user !== false); + $this->assertArrayHasKey('user_id', $user); + $this->assertArrayHasKey('first_name', $user); + $this->assertArrayHasKey('last_name', $user); + $this->assertEquals($user['user_id'], 'testusername'); + $this->assertEquals($user['first_name'], 'Test'); + $this->assertEquals($user['last_name'], 'User'); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php new file mode 100644 index 00000000..71cca3bd --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php @@ -0,0 +1,59 @@ + 'ThisIsMyAccessToken' + )); + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals($param, 'ThisIsMyAccessToken'); + } + + public function testInvalidContentType() + { + $bearer = new Bearer(); + $request = TestRequest::createPost(array( + 'access_token' => 'ThisIsMyAccessToken' + )); + $request->server['CONTENT_TYPE'] = 'application/json; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertNull($param); + $this->assertEquals($response->getStatusCode(), 400); + $this->assertEquals($response->getParameter('error'), 'invalid_request'); + $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); + } + + public function testValidRequestUsingAuthorizationHeader() + { + $bearer = new Bearer(); + $request = new TestRequest(); + $request->headers['AUTHORIZATION'] = 'Bearer MyToken'; + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals('MyToken', $param); + } + + public function testValidRequestUsingAuthorizationHeaderCaseInsensitive() + { + $bearer = new Bearer(); + $request = new TestRequest(); + $request->server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + $request->headers['Authorization'] = 'Bearer MyToken'; + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals('MyToken', $param); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php new file mode 100644 index 00000000..0a4af071 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/bootstrap.php @@ -0,0 +1,12 @@ +cleanupTravisDynamoDb(); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa new file mode 100644 index 00000000..e8b9eff2 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV +8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN +QDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB +AoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA ++WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w +C6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb +oaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56 +1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0 +CPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg +VHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr +uxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo +cj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA +HoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub new file mode 100644 index 00000000..1ac15f5e --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/keys/id_rsa.pub @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe +Fw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf +MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs +NjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw +izIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu +Be+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K +vCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh +dGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD +lM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl +aViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL +FRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json new file mode 100644 index 00000000..52d3f239 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/config/storage.json @@ -0,0 +1,188 @@ +{ + "authorization_codes": { + "testcode": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "id_token": "IDTOKEN" + }, + "testcode-with-scope": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "scope": "scope1 scope2" + }, + "testcode-expired": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "1356998400" + }, + "testcode-empty-secret": { + "client_id": "Test Client ID Empty Secret", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999" + }, + "testcode-openid": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "", + "expires": "9999999999", + "id_token": "test_id_token" + }, + "testcode-redirect-uri": { + "client_id": "Test Client ID", + "user_id": "", + "redirect_uri": "http://brentertainment.com/voil%C3%A0", + "expires": "9999999999", + "id_token": "IDTOKEN" + } + }, + "client_credentials" : { + "Test Client ID": { + "client_secret": "TestSecret" + }, + "Test Client ID with Redirect Uri": { + "client_secret": "TestSecret2", + "redirect_uri": "http://brentertainment.com" + }, + "Test Client ID with Buggy Redirect Uri": { + "client_secret": "TestSecret2", + "redirect_uri": " http://brentertainment.com" + }, + "Test Client ID with Multiple Redirect Uris": { + "client_secret": "TestSecret3", + "redirect_uri": "http://brentertainment.com http://morehazards.com" + }, + "Test Client ID with Redirect Uri Parts": { + "client_secret": "TestSecret4", + "redirect_uri": "http://user:pass@brentertainment.com:2222/authorize/cb?auth_type=oauth&test=true" + }, + "Test Some Other Client": { + "client_secret": "TestSecret3" + }, + "Test Client ID Empty Secret": { + "client_secret": "" + }, + "Test Client ID For Password Grant": { + "grant_types": "password", + "client_secret": "" + }, + "Client ID With User ID": { + "client_secret": "TestSecret", + "user_id": "brent@brentertainment.com" + }, + "oauth_test_client": { + "client_secret": "testpass", + "grant_types": "implicit password" + } + }, + "user_credentials" : { + "test-username": { + "password": "testpass" + }, + "testusername": { + "password": "testpass" + }, + "testuser": { + "password": "password", + "email": "testuser@test.com", + "email_verified": true + }, + "johndoe": { + "password": "password" + } + }, + "refresh_tokens" : { + "test-refreshtoken": { + "refresh_token": "test-refreshtoken", + "client_id": "Test Client ID", + "user_id": "test-username", + "expires": 0, + "scope": null + }, + "test-refreshtoken-with-scope": { + "refresh_token": "test-refreshtoken", + "client_id": "Test Client ID", + "user_id": "test-username", + "expires": 0, + "scope": "scope1 scope2" + } + }, + "access_tokens" : { + "accesstoken-expired": { + "access_token": "accesstoken-expired", + "client_id": "Test Client ID", + "expires": 1234567, + "scope": null + }, + "accesstoken-scope": { + "access_token": "accesstoken-scope", + "client_id": "Test Client ID", + "expires": 99999999900, + "scope": "testscope" + }, + "accesstoken-openid-connect": { + "access_token": "accesstoken-openid-connect", + "client_id": "Test Client ID", + "user_id": "testuser", + "expires": 99999999900, + "scope": "openid email" + }, + "accesstoken-malformed": { + "access_token": "accesstoken-mallformed", + "expires": 99999999900, + "scope": "testscope" + } + }, + "jwt": { + "Test Client ID": { + "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5/SxVlE8gnpFqCxgl2wjhzY7u\ncEi00s0kUg3xp7lVEvgLgYcAnHiWp+gtSjOFfH2zsvpiWm6Lz5f743j/FEzHIO1o\nwR0p4d9pOaJK07d01+RzoQLOIQAgXrr4T1CCWUesncwwPBVCyy2Mw3Nmhmr9MrF8\nUlvdRKBxriRnlP3qJQIDAQAB\n-----END PUBLIC KEY-----", + "subject": "testuser@ourdomain.com" + }, + "Test Client ID PHP-5.2": { + "key": "mysecretkey", + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client": { + "key": null, + "subject": "testuser@ourdomain.com" + }, + "Missing Key Client PHP-5.2": { + "key": null, + "subject": "testuser@ourdomain.com" + }, + "oauth_test_client": { + "key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----", + "subject": "test_subject" + } + }, + "jti": [ + { + "issuer": "Test Client ID", + "subject": "testuser@ourdomain.com", + "audience": "http://myapp.com/oauth/auth", + "expires": 99999999900, + "jti": "used_jti" + } + ], + "supported_scopes" : [ + "scope1", + "scope2", + "scope3", + "clientscope1", + "clientscope2", + "clientscope3", + "supportedscope1", + "supportedscope2", + "supportedscope3", + "supportedscope4" + ], + "keys": { + "public_key": "-----BEGIN CERTIFICATE-----\nMIICiDCCAfGgAwIBAgIBADANBgkqhkiG9w0BAQQFADA9MQswCQYDVQQGEwJVUzEL\nMAkGA1UECBMCVVQxITAfBgNVBAoTGFZpZ25ldHRlIENvcnBvcmF0aW9uIFNCWDAe\nFw0xMTEwMTUwMzE4MjdaFw0zMTEwMTAwMzE4MjdaMD0xCzAJBgNVBAYTAlVTMQsw\nCQYDVQQIEwJVVDEhMB8GA1UEChMYVmlnbmV0dGUgQ29ycG9yYXRpb24gU0JYMIGf\nMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLs\nNjP+uAt2eO0cc5J9H5XV8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdw\nizIum8j0KzpsGYH5qReNQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJu\nBe+FQpZTs8DewwIDAQABo4GXMIGUMB0GA1UdDgQWBBRe8hrEXm+Yim4YlD5Nx+1K\nvCYs9DBlBgNVHSMEXjBcgBRe8hrEXm+Yim4YlD5Nx+1KvCYs9KFBpD8wPTELMAkG\nA1UEBhMCVVMxCzAJBgNVBAgTAlVUMSEwHwYDVQQKExhWaWduZXR0ZSBDb3Jwb3Jh\ndGlvbiBTQliCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBjhyRD\nlM7vnLn6drgQVftW5V9nDFAyPAuiGvMIKFSbiAf1PxXCRn5sfJquwWKsJUi4ZGNl\naViXdFmN6/F13PSM+yg63tpKy0fYqMbTM+Oe5WuSHkSW1VuYNHV+24adgNk/FRDL\nFRrlM1f6s9VTLWvwGItjfrof0Ba8Uq7ZDSb9Xg==\n-----END CERTIFICATE-----", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC8fpi06NfVYHAOAnxNMVnTXr/ptsLsNjP+uAt2eO0cc5J9H5XV\n8lFVujOrRu/JWi1TDmAvOaf/6A3BphIA1Pwp0AAqlZdwizIum8j0KzpsGYH5qReN\nQDwF3oUSKMsQCCGCDHrDYifG/pRi9bN1ZVjEXPr35HJuBe+FQpZTs8DewwIDAQAB\nAoGARfNxNknmtx/n1bskZ/01iZRzAge6BLEE0LV6Q4gS7mkRZu/Oyiv39Sl5vUlA\n+WdGxLjkBwKNjxGN8Vxw9/ASd8rSsqeAUYIwAeifXrHhj5DBPQT/pDPkeFnp9B1w\nC6jo+3AbBQ4/b0ONSIEnCL2xGGglSIAxO17T1ViXp7lzXPECQQDe63nkRdWM0OCb\noaHQPT3E26224maIstrGFUdt9yw3yJf4bOF7TtiPLlLuHsTTge3z+fG6ntC0xG56\n1cl37C3ZAkEA2HdVcRGugNp/qmVz4LJTpD+WZKi73PLAO47wDOrYh9Pn2I6fcEH0\nCPnggt1ko4ujvGzFTvRH64HXa6aPCv1j+wJBAMQMah3VQPNf/DlDVFEUmw9XeBZg\nVHaifX851aEjgXLp6qVj9IYCmLiLsAmVa9rr6P7p8asD418nZlaHUHE0eDkCQQCr\nuxis6GMx1Ka971jcJX2X696LoxXPd0KsvXySMupv79yagKPa8mgBiwPjrnK+EPVo\ncj6iochA/bSCshP/mwFrAkBHEKPi6V6gb94JinCT7x3weahbdp6bJ6/nzBH/p9VA\nHoT1JtwNFhGv9BCjmDydshQHfSWpY9NxlccBKL7ITm8R\n-----END RSA PRIVATE KEY-----" + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php new file mode 100644 index 00000000..a916ff2a --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Request/TestRequest.php @@ -0,0 +1,66 @@ +query = $_GET; + $this->request = $_POST; + $this->server = $_SERVER; + $this->headers = array(); + } + + 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 getAllQueryParameters() + { + return $this->query; + } + + public function setQuery(array $query) + { + $this->query = $query; + } + + public function setMethod($method) + { + $this->server['REQUEST_METHOD'] = $method; + } + + public function setPost(array $params) + { + $this->setMethod('POST'); + $this->request = $params; + } + + public static function createPost(array $params = array()) + { + $request = new self(); + $request->setPost($params); + + return $request; + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php new file mode 100755 index 00000000..e841d3ad --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/BaseTest.php @@ -0,0 +1,38 @@ +getMemoryStorage(); + $sqlite = Bootstrap::getInstance()->getSqlitePdo(); + $mysql = Bootstrap::getInstance()->getMysqlPdo(); + $postgres = Bootstrap::getInstance()->getPostgresPdo(); + $mongo = Bootstrap::getInstance()->getMongo(); + $mongoDb = Bootstrap::getInstance()->getMongoDB(); + $redis = Bootstrap::getInstance()->getRedisStorage(); + $cassandra = Bootstrap::getInstance()->getCassandraStorage(); + $dynamodb = Bootstrap::getInstance()->getDynamoDbStorage(); + $couchbase = Bootstrap::getInstance()->getCouchbase(); + + /* hack until we can fix "default_scope" dependencies in other tests */ + $memory->defaultScope = 'defaultscope1 defaultscope2'; + + return array( + array($memory), + array($sqlite), + array($mysql), + array($postgres), + array($mongo), + array($mongoDb), + array($redis), + array($cassandra), + array($dynamodb), + array($couchbase), + ); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php new file mode 100755 index 00000000..8e428f9b --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php @@ -0,0 +1,967 @@ +configDir = __DIR__.'/../../../config'; + } + + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function getSqlitePdo() + { + if (!$this->sqlite) { + $this->removeSqliteDb(); + $pdo = new \PDO(sprintf('sqlite:%s', $this->getSqliteDir())); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->createSqliteDb($pdo); + + $this->sqlite = new Pdo($pdo); + } + + return $this->sqlite; + } + + public function getPostgresPdo() + { + if (!$this->postgres) { + if (in_array('pgsql', \PDO::getAvailableDrivers())) { + $this->removePostgresDb(); + $this->createPostgresDb(); + if ($pdo = $this->getPostgresDriver()) { + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->populatePostgresDb($pdo); + $this->postgres = new Pdo($pdo); + } + } else { + $this->postgres = new NullStorage('Postgres', 'Missing postgres PDO extension.'); + } + } + + return $this->postgres; + } + + public function getPostgresDriver() + { + try { + $pdo = new \PDO('pgsql:host=localhost;dbname=oauth2_server_php', 'postgres'); + + return $pdo; + } catch (\PDOException $e) { + $this->postgres = new NullStorage('Postgres', $e->getMessage()); + } + } + + public function getMemoryStorage() + { + return new Memory(json_decode(file_get_contents($this->configDir. '/storage.json'), true)); + } + + public function getRedisStorage() + { + if (!$this->redis) { + if (class_exists('Predis\Client')) { + $redis = new \Predis\Client(); + if ($this->testRedisConnection($redis)) { + $redis->flushdb(); + $this->redis = new Redis($redis); + $this->createRedisDb($this->redis); + } else { + $this->redis = new NullStorage('Redis', 'Unable to connect to redis server on port 6379'); + } + } else { + $this->redis = new NullStorage('Redis', 'Missing redis library. Please run "composer.phar require predis/predis:dev-master"'); + } + } + + return $this->redis; + } + + private function testRedisConnection(\Predis\Client $redis) + { + try { + $redis->connect(); + } catch (\Predis\CommunicationException $exception) { + // we were unable to connect to the redis server + return false; + } + + return true; + } + + public function getMysqlPdo() + { + if (!$this->mysql) { + $pdo = null; + try { + $pdo = new \PDO('mysql:host=localhost;', 'root'); + } catch (\PDOException $e) { + $this->mysql = new NullStorage('MySQL', 'Unable to connect to MySQL on root@localhost'); + } + + if ($pdo) { + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->removeMysqlDb($pdo); + $this->createMysqlDb($pdo); + + $this->mysql = new Pdo($pdo); + } + } + + return $this->mysql; + } + + public function getMongo() + { + if (!$this->mongo) { + if (class_exists('MongoClient')) { + $mongo = new \MongoClient('mongodb://localhost:27017', array('connect' => false)); + if ($this->testMongoConnection($mongo)) { + $db = $mongo->oauth2_server_php_legacy; + $this->removeMongo($db); + $this->createMongo($db); + + $this->mongo = new Mongo($db); + } else { + $this->mongo = new NullStorage('Mongo', 'Unable to connect to mongo server on "localhost:27017"'); + } + } else { + $this->mongo = new NullStorage('Mongo', 'Missing mongo php extension. Please install mongo.so'); + } + } + + return $this->mongo; + } + + public function getMongoDb() + { + if (!$this->mongoDb) { + if (class_exists('MongoDB\Client')) { + $mongoDb = new \MongoDB\Client('mongodb://localhost:27017'); + if ($this->testMongoDBConnection($mongoDb)) { + $db = $mongoDb->oauth2_server_php; + $this->removeMongoDb($db); + $this->createMongoDb($db); + + $this->mongoDb = new MongoDB($db); + } else { + $this->mongoDb = new NullStorage('MongoDB', 'Unable to connect to mongo server on "localhost:27017"'); + } + } else { + $this->mongoDb = new NullStorage('MongoDB', 'Missing MongoDB php extension. Please install mongodb.so'); + } + } + + return $this->mongoDb; + } + + private function testMongoConnection(\MongoClient $mongo) + { + try { + $mongo->connect(); + } catch (\MongoConnectionException $e) { + return false; + } + + return true; + } + + private function testMongoDBConnection(\MongoDB\Client $mongo) + { + return true; + } + + public function getCouchbase() + { + if (!$this->couchbase) { + if ($this->getEnvVar('SKIP_COUCHBASE_TESTS')) { + $this->couchbase = new NullStorage('Couchbase', 'Skipping Couchbase tests'); + } elseif (!class_exists('Couchbase')) { + $this->couchbase = new NullStorage('Couchbase', 'Missing Couchbase php extension. Please install couchbase.so'); + } else { + // round-about way to make sure couchbase is working + // this is required because it throws a "floating point exception" otherwise + $code = "new \Couchbase(array('localhost:8091'), '', '', 'auth', false);"; + $exec = sprintf('php -r "%s"', $code); + $ret = exec($exec, $test, $var); + if ($ret != 0) { + $couchbase = new \Couchbase(array('localhost:8091'), '', '', 'auth', false); + if ($this->testCouchbaseConnection($couchbase)) { + $this->clearCouchbase($couchbase); + $this->createCouchbaseDB($couchbase); + + $this->couchbase = new CouchbaseDB($couchbase); + } else { + $this->couchbase = new NullStorage('Couchbase', 'Unable to connect to Couchbase server on "localhost:8091"'); + } + } else { + $this->couchbase = new NullStorage('Couchbase', 'Error while trying to connect to Couchbase'); + } + } + } + + return $this->couchbase; + } + + private function testCouchbaseConnection(\Couchbase $couchbase) + { + try { + if (count($couchbase->getServers()) > 0) { + return true; + } + } catch (\CouchbaseException $e) { + return false; + } + + return true; + } + + public function getCassandraStorage() + { + if (!$this->cassandra) { + if (class_exists('phpcassa\ColumnFamily')) { + $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_test', array('127.0.0.1:9160')); + if ($this->testCassandraConnection($cassandra)) { + $this->removeCassandraDb(); + $this->cassandra = new Cassandra($cassandra); + $this->createCassandraDb($this->cassandra); + } else { + $this->cassandra = new NullStorage('Cassandra', 'Unable to connect to cassandra server on "127.0.0.1:9160"'); + } + } else { + $this->cassandra = new NullStorage('Cassandra', 'Missing cassandra library. Please run "composer.phar require thobbs/phpcassa:dev-master"'); + } + } + + return $this->cassandra; + } + + private function testCassandraConnection(\phpcassa\Connection\ConnectionPool $cassandra) + { + try { + new \phpcassa\SystemManager('localhost:9160'); + } catch (\Exception $e) { + return false; + } + + return true; + } + + private function removeCassandraDb() + { + $sys = new \phpcassa\SystemManager('localhost:9160'); + + try { + $sys->drop_keyspace('oauth2_test'); + } catch (\cassandra\InvalidRequestException $e) { + + } + } + + private function createCassandraDb(Cassandra $storage) + { + // create the cassandra keyspace and column family + $sys = new \phpcassa\SystemManager('localhost:9160'); + + $sys->create_keyspace('oauth2_test', array( + "strategy_class" => \phpcassa\Schema\StrategyClass::SIMPLE_STRATEGY, + "strategy_options" => array('replication_factor' => '1') + )); + + $sys->create_column_family('oauth2_test', 'auth'); + $cassandra = new \phpcassa\Connection\ConnectionPool('oauth2_test', array('127.0.0.1:9160')); + $cf = new \phpcassa\ColumnFamily($cassandra, 'auth'); + + // populate the data + $storage->setClientDetails("oauth_test_client", "testpass", "http://example.com", 'implicit password'); + $storage->setAccessToken("testtoken", "Some Client", '', time() + 1000); + $storage->setAuthorizationCode("testcode", "Some Client", '', '', time() + 1000); + + $storage->setScope('supportedscope1 supportedscope2 supportedscope3 supportedscope4'); + $storage->setScope('defaultscope1 defaultscope2', null, 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Client ID 2'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID 2', 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Default Scope Client ID 2'); + $storage->setScope('clientscope3', 'Test Default Scope Client ID 2', 'default'); + + $storage->setClientKey('oauth_test_client', $this->getTestPublicKey(), 'test_subject'); + + $cf->insert("oauth_public_keys:ClientID_One", array('__data' => json_encode(array("public_key" => "client_1_public", "private_key" => "client_1_private", "encryption_algorithm" => "RS256")))); + $cf->insert("oauth_public_keys:ClientID_Two", array('__data' => json_encode(array("public_key" => "client_2_public", "private_key" => "client_2_private", "encryption_algorithm" => "RS256")))); + $cf->insert("oauth_public_keys:", array('__data' => json_encode(array("public_key" => $this->getTestPublicKey(), "private_key" => $this->getTestPrivateKey(), "encryption_algorithm" => "RS256")))); + + $cf->insert("oauth_users:testuser", array('__data' =>json_encode(array("password" => "password", "email" => "testuser@test.com", "email_verified" => true)))); + + } + + private function createSqliteDb(\PDO $pdo) + { + $this->runPdoSql($pdo); + } + + private function removeSqliteDb() + { + if (file_exists($this->getSqliteDir())) { + unlink($this->getSqliteDir()); + } + } + + private function createMysqlDb(\PDO $pdo) + { + $pdo->exec('CREATE DATABASE oauth2_server_php'); + $pdo->exec('USE oauth2_server_php'); + $this->runPdoSql($pdo); + } + + private function removeMysqlDb(\PDO $pdo) + { + $pdo->exec('DROP DATABASE IF EXISTS oauth2_server_php'); + } + + private function createPostgresDb() + { + if (!`psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='postgres'"`) { + `createuser -s -r postgres`; + } + + `createdb -O postgres oauth2_server_php`; + } + + private function populatePostgresDb(\PDO $pdo) + { + $this->runPdoSql($pdo); + } + + private function removePostgresDb() + { + if (trim(`psql -l | grep oauth2_server_php | wc -l`)) { + `dropdb oauth2_server_php`; + } + } + + public function runPdoSql(\PDO $pdo) + { + $storage = new Pdo($pdo); + foreach (explode(';', $storage->getBuildSql()) as $statement) { + $result = $pdo->exec($statement); + } + + // set up scopes + $sql = 'INSERT INTO oauth_scopes (scope) VALUES (?)'; + foreach (explode(' ', 'supportedscope1 supportedscope2 supportedscope3 supportedscope4 clientscope1 clientscope2 clientscope3') as $supportedScope) { + $pdo->prepare($sql)->execute(array($supportedScope)); + } + + $sql = 'INSERT INTO oauth_scopes (scope, is_default) VALUES (?, ?)'; + foreach (array('defaultscope1', 'defaultscope2') as $defaultScope) { + $pdo->prepare($sql)->execute(array($defaultScope, true)); + } + + // set up clients + $sql = 'INSERT INTO oauth_clients (client_id, client_secret, scope, grant_types) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('Test Client ID', 'TestSecret', 'clientscope1 clientscope2', null)); + $pdo->prepare($sql)->execute(array('Test Client ID 2', 'TestSecret', 'clientscope1 clientscope2 clientscope3', null)); + $pdo->prepare($sql)->execute(array('Test Default Scope Client ID', 'TestSecret', 'clientscope1 clientscope2', null)); + $pdo->prepare($sql)->execute(array('oauth_test_client', 'testpass', null, 'implicit password')); + + // set up misc + $sql = 'INSERT INTO oauth_access_tokens (access_token, client_id, expires, user_id) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testtoken', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), null)); + $pdo->prepare($sql)->execute(array('accesstoken-openid-connect', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')), 'testuser')); + + $sql = 'INSERT INTO oauth_authorization_codes (authorization_code, client_id, expires) VALUES (?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testcode', 'Some Client', date('Y-m-d H:i:s', strtotime('+1 hour')))); + + $sql = 'INSERT INTO oauth_users (username, password, email, email_verified) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('testuser', 'password', 'testuser@test.com', true)); + + $sql = 'INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array('ClientID_One', 'client_1_public', 'client_1_private', 'RS256')); + $pdo->prepare($sql)->execute(array('ClientID_Two', 'client_2_public', 'client_2_private', 'RS256')); + + $sql = 'INSERT INTO oauth_public_keys (client_id, public_key, private_key, encryption_algorithm) VALUES (?, ?, ?, ?)'; + $pdo->prepare($sql)->execute(array(null, $this->getTestPublicKey(), $this->getTestPrivateKey(), 'RS256')); + + $sql = 'INSERT INTO oauth_jwt (client_id, subject, public_key) VALUES (?, ?, ?)'; + $pdo->prepare($sql)->execute(array('oauth_test_client', 'test_subject', $this->getTestPublicKey())); + } + + public function getSqliteDir() + { + return $this->configDir. '/test.sqlite'; + } + + public function getConfigDir() + { + return $this->configDir; + } + + private function createCouchbaseDB(\Couchbase $db) + { + $db->set('oauth_clients-oauth_test_client',json_encode(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + ))); + + $db->set('oauth_access_tokens-testtoken',json_encode(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + ))); + + $db->set('oauth_authorization_codes-testcode',json_encode(array( + 'access_token' => "testcode", + 'client_id' => "Some Client" + ))); + + $db->set('oauth_users-testuser',json_encode(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + ))); + + $db->set('oauth_jwt-oauth_test_client',json_encode(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + ))); + } + + private function clearCouchbase(\Couchbase $cb) + { + $cb->delete('oauth_authorization_codes-new-openid-code'); + $cb->delete('oauth_access_tokens-newtoken'); + $cb->delete('oauth_authorization_codes-newcode'); + $cb->delete('oauth_refresh_tokens-refreshtoken'); + } + + private function createMongo(\MongoDB $db) + { + $db->oauth_clients->insert(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + )); + + $db->oauth_access_tokens->insert(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + )); + + $db->oauth_authorization_codes->insert(array( + 'authorization_code' => "testcode", + 'client_id' => "Some Client" + )); + + $db->oauth_users->insert(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + )); + + $db->oauth_keys->insert(array( + 'client_id' => null, + 'public_key' => $this->getTestPublicKey(), + 'private_key' => $this->getTestPrivateKey(), + 'encryption_algorithm' => 'RS256' + )); + + $db->oauth_jwt->insert(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + )); + } + + public function removeMongo(\MongoDB $db) + { + $db->drop(); + } + + private function createMongoDB(\MongoDB\Database $db) + { + $db->oauth_clients->insertOne(array( + 'client_id' => "oauth_test_client", + 'client_secret' => "testpass", + 'redirect_uri' => "http://example.com", + 'grant_types' => 'implicit password' + )); + + $db->oauth_access_tokens->insertOne(array( + 'access_token' => "testtoken", + 'client_id' => "Some Client" + )); + + $db->oauth_authorization_codes->insertOne(array( + 'authorization_code' => "testcode", + 'client_id' => "Some Client" + )); + + $db->oauth_users->insertOne(array( + 'username' => 'testuser', + 'password' => 'password', + 'email' => 'testuser@test.com', + 'email_verified' => true, + )); + + $db->oauth_keys->insertOne(array( + 'client_id' => null, + 'public_key' => $this->getTestPublicKey(), + 'private_key' => $this->getTestPrivateKey(), + 'encryption_algorithm' => 'RS256' + )); + + $db->oauth_jwt->insertOne(array( + 'client_id' => 'oauth_test_client', + 'key' => $this->getTestPublicKey(), + 'subject' => 'test_subject', + )); + } + + public function removeMongoDB(\MongoDB\Database $db) + { + $db->drop(); + } + + private function createRedisDb(Redis $storage) + { + $storage->setClientDetails("oauth_test_client", "testpass", "http://example.com", 'implicit password'); + $storage->setAccessToken("testtoken", "Some Client", '', time() + 1000); + $storage->setAuthorizationCode("testcode", "Some Client", '', '', time() + 1000); + $storage->setUser("testuser", "password"); + + $storage->setScope('supportedscope1 supportedscope2 supportedscope3 supportedscope4'); + $storage->setScope('defaultscope1 defaultscope2', null, 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Client ID 2'); + $storage->setScope('clientscope1 clientscope2', 'Test Client ID 2', 'default'); + + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID'); + $storage->setScope('clientscope1 clientscope2', 'Test Default Scope Client ID', 'default'); + + $storage->setScope('clientscope1 clientscope2 clientscope3', 'Test Default Scope Client ID 2'); + $storage->setScope('clientscope3', 'Test Default Scope Client ID 2', 'default'); + + $storage->setClientKey('oauth_test_client', $this->getTestPublicKey(), 'test_subject'); + } + + public function getTestPublicKey() + { + return file_get_contents(__DIR__.'/../../../config/keys/id_rsa.pub'); + } + + private function getTestPrivateKey() + { + return file_get_contents(__DIR__.'/../../../config/keys/id_rsa'); + } + + public function getDynamoDbStorage() + { + if (!$this->dynamodb) { + // only run once per travis build + if (true == $this->getEnvVar('TRAVIS')) { + if (self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) { + $this->dynamodb = new NullStorage('DynamoDb', 'Skipping for travis.ci - only run once per build'); + + return; + } + } + if (class_exists('\Aws\DynamoDb\DynamoDbClient')) { + if ($client = $this->getDynamoDbClient()) { + // travis runs a unique set of tables per build, to avoid conflict + $prefix = ''; + if ($build_id = $this->getEnvVar('TRAVIS_JOB_NUMBER')) { + $prefix = sprintf('build_%s_', $build_id); + } else { + if (!$this->deleteDynamoDb($client, $prefix, true)) { + return $this->dynamodb = new NullStorage('DynamoDb', 'Timed out while waiting for DynamoDB deletion (30 seconds)'); + } + } + $this->createDynamoDb($client, $prefix); + $this->populateDynamoDb($client, $prefix); + $config = array( + 'client_table' => $prefix.'oauth_clients', + 'access_token_table' => $prefix.'oauth_access_tokens', + 'refresh_token_table' => $prefix.'oauth_refresh_tokens', + 'code_table' => $prefix.'oauth_authorization_codes', + 'user_table' => $prefix.'oauth_users', + 'jwt_table' => $prefix.'oauth_jwt', + 'scope_table' => $prefix.'oauth_scopes', + 'public_key_table' => $prefix.'oauth_public_keys', + ); + $this->dynamodb = new DynamoDB($client, $config); + } elseif (!$this->dynamodb) { + $this->dynamodb = new NullStorage('DynamoDb', 'unable to connect to DynamoDB'); + } + } else { + $this->dynamodb = new NullStorage('DynamoDb', 'Missing DynamoDB library. Please run "composer.phar require aws/aws-sdk-php:dev-master'); + } + } + + return $this->dynamodb; + } + + private function getDynamoDbClient() + { + $config = array(); + // check for environment variables + if (($key = $this->getEnvVar('AWS_ACCESS_KEY_ID')) && ($secret = $this->getEnvVar('AWS_SECRET_KEY'))) { + $config['key'] = $key; + $config['secret'] = $secret; + } else { + // fall back on ~/.aws/credentials file + // @see http://docs.aws.amazon.com/aws-sdk-php/guide/latest/credentials.html#credential-profiles + if (!file_exists($this->getEnvVar('HOME') . '/.aws/credentials')) { + $this->dynamodb = new NullStorage('DynamoDb', 'No aws credentials file found, and no AWS_ACCESS_KEY_ID or AWS_SECRET_KEY environment variable set'); + + return; + } + + // set profile in AWS_PROFILE environment variable, defaults to "default" + $config['profile'] = $this->getEnvVar('AWS_PROFILE', 'default'); + } + + // set region in AWS_REGION environment variable, defaults to "us-east-1" + $config['region'] = $this->getEnvVar('AWS_REGION', \Aws\Common\Enum\Region::US_EAST_1); + + return \Aws\DynamoDb\DynamoDbClient::factory($config); + } + + private function deleteDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null, $waitForDeletion = false) + { + $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users'); + $nbTables = count($tablesList); + + // Delete all table. + foreach ($tablesList as $key => $table) { + try { + $client->deleteTable(array('TableName' => $prefix.$table)); + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + } + } + + // Wait for deleting + if ($waitForDeletion) { + $retries = 5; + $nbTableDeleted = 0; + while ($nbTableDeleted != $nbTables) { + $nbTableDeleted = 0; + foreach ($tablesList as $key => $table) { + try { + $result = $client->describeTable(array('TableName' => $prefix.$table)); + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + $nbTableDeleted++; + } + } + if ($nbTableDeleted != $nbTables) { + if ($retries < 0) { + // we are tired of waiting + return false; + } + sleep(5); + echo "Sleeping 5 seconds for DynamoDB ($retries more retries)...\n"; + $retries--; + } + } + } + + return true; + } + + private function createDynamoDb(\Aws\DynamoDb\DynamoDbClient $client, $prefix = null) + { + $tablesList = explode(' ', 'oauth_access_tokens oauth_authorization_codes oauth_clients oauth_jwt oauth_public_keys oauth_refresh_tokens oauth_scopes oauth_users'); + $nbTables = count($tablesList); + $client->createTable(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'AttributeDefinitions' => array( + array('AttributeName' => 'access_token','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'access_token','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_authorization_codes', + 'AttributeDefinitions' => array( + array('AttributeName' => 'authorization_code','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'authorization_code','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_clients', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_jwt', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S'), + array('AttributeName' => 'subject','AttributeType' => 'S') + ), + 'KeySchema' => array( + array('AttributeName' => 'client_id','KeyType' => 'HASH'), + array('AttributeName' => 'subject','KeyType' => 'RANGE') + ), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_public_keys', + 'AttributeDefinitions' => array( + array('AttributeName' => 'client_id','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'client_id','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_refresh_tokens', + 'AttributeDefinitions' => array( + array('AttributeName' => 'refresh_token','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'refresh_token','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_scopes', + 'AttributeDefinitions' => array( + array('AttributeName' => 'scope','AttributeType' => 'S'), + array('AttributeName' => 'is_default','AttributeType' => 'S') + ), + 'KeySchema' => array(array('AttributeName' => 'scope','KeyType' => 'HASH')), + 'GlobalSecondaryIndexes' => array( + array( + 'IndexName' => 'is_default-index', + 'KeySchema' => array(array('AttributeName' => 'is_default', 'KeyType' => 'HASH')), + 'Projection' => array('ProjectionType' => 'ALL'), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + ), + ), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + $client->createTable(array( + 'TableName' => $prefix.'oauth_users', + 'AttributeDefinitions' => array(array('AttributeName' => 'username','AttributeType' => 'S')), + 'KeySchema' => array(array('AttributeName' => 'username','KeyType' => 'HASH')), + 'ProvisionedThroughput' => array('ReadCapacityUnits' => 1,'WriteCapacityUnits' => 1) + )); + + // Wait for creation + $nbTableCreated = 0; + while ($nbTableCreated != $nbTables) { + $nbTableCreated = 0; + foreach ($tablesList as $key => $table) { + try { + $result = $client->describeTable(array('TableName' => $prefix.$table)); + if ($result['Table']['TableStatus'] == 'ACTIVE') { + $nbTableCreated++; + } + } catch (\Aws\DynamoDb\Exception\DynamoDbException $e) { + // Table does not exist : nothing to do + $nbTableCreated++; + } + } + if ($nbTableCreated != $nbTables) { + sleep(1); + } + } + } + + private function populateDynamoDb($client, $prefix = null) + { + // set up scopes + foreach (explode(' ', 'supportedscope1 supportedscope2 supportedscope3 supportedscope4 clientscope1 clientscope2 clientscope3') as $supportedScope) { + $client->putItem(array( + 'TableName' => $prefix.'oauth_scopes', + 'Item' => array('scope' => array('S' => $supportedScope)) + )); + } + + foreach (array('defaultscope1', 'defaultscope2') as $defaultScope) { + $client->putItem(array( + 'TableName' => $prefix.'oauth_scopes', + 'Item' => array('scope' => array('S' => $defaultScope), 'is_default' => array('S' => "true")) + )); + } + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Client ID'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Client ID 2'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2 clientscope3') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'Test Default Scope Client ID'), + 'client_secret' => array('S' => 'TestSecret'), + 'scope' => array('S' => 'clientscope1 clientscope2') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_clients', + 'Item' => array( + 'client_id' => array('S' => 'oauth_test_client'), + 'client_secret' => array('S' => 'testpass'), + 'grant_types' => array('S' => 'implicit password') + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'Item' => array( + 'access_token' => array('S' => 'testtoken'), + 'client_id' => array('S' => 'Some Client'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_access_tokens', + 'Item' => array( + 'access_token' => array('S' => 'accesstoken-openid-connect'), + 'client_id' => array('S' => 'Some Client'), + 'user_id' => array('S' => 'testuser'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_authorization_codes', + 'Item' => array( + 'authorization_code' => array('S' => 'testcode'), + 'client_id' => array('S' => 'Some Client'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_users', + 'Item' => array( + 'username' => array('S' => 'testuser'), + 'password' => array('S' => 'password'), + 'email' => array('S' => 'testuser@test.com'), + 'email_verified' => array('S' => 'true'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => 'ClientID_One'), + 'public_key' => array('S' => 'client_1_public'), + 'private_key' => array('S' => 'client_1_private'), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => 'ClientID_Two'), + 'public_key' => array('S' => 'client_2_public'), + 'private_key' => array('S' => 'client_2_private'), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_public_keys', + 'Item' => array( + 'client_id' => array('S' => '0'), + 'public_key' => array('S' => $this->getTestPublicKey()), + 'private_key' => array('S' => $this->getTestPrivateKey()), + 'encryption_algorithm' => array('S' => 'RS256'), + ) + )); + + $client->putItem(array( + 'TableName' => $prefix.'oauth_jwt', + 'Item' => array( + 'client_id' => array('S' => 'oauth_test_client'), + 'subject' => array('S' => 'test_subject'), + 'public_key' => array('S' => $this->getTestPublicKey()), + ) + )); + } + + public function cleanupTravisDynamoDb($prefix = null) + { + if (is_null($prefix)) { + // skip this when not applicable + if (!$this->getEnvVar('TRAVIS') || self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) { + return; + } + + $prefix = sprintf('build_%s_', $this->getEnvVar('TRAVIS_JOB_NUMBER')); + } + + $client = $this->getDynamoDbClient(); + $this->deleteDynamoDb($client, $prefix); + } + + private function getEnvVar($var, $default = null) + { + return isset($_SERVER[$var]) ? $_SERVER[$var] : (getenv($var) ?: $default); + } +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php new file mode 100644 index 00000000..6caa6206 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/NullStorage.php @@ -0,0 +1,32 @@ +name = $name; + $this->description = $description; + } + + public function __toString() + { + return $this->name; + } + + public function getMessage() + { + if ($this->description) { + return $this->description; + } + + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/composer/autoload_namespaces.php b/data/web/inc/lib/vendor/composer/autoload_namespaces.php index b7fc0125..39b50a10 100644 --- a/data/web/inc/lib/vendor/composer/autoload_namespaces.php +++ b/data/web/inc/lib/vendor/composer/autoload_namespaces.php @@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'OAuth2' => array($vendorDir . '/bshaffer/oauth2-server-php/src'), ); diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 7bcc3ed6..efd7776c 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -57,6 +57,16 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b ), ); + public static $prefixesPsr0 = array ( + 'O' => + array ( + 'OAuth2' => + array ( + 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', + ), + ), + ); + public static $classMap = array ( 'EasyPeasyICS' => __DIR__ . '/..' . '/phpmailer/phpmailer/extras/EasyPeasyICS.php', 'PHPMailer' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmailer.php', @@ -78,6 +88,7 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$prefixesPsr0; $loader->classMap = ComposerStaticInit873464e4bd965a3168f133248b1b218b::$classMap; }, null, ClassLoader::class); diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index 42f210d0..46e7b621 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1,17 +1,77 @@ [ { - "name": "ddeboer/imap", - "version": "1.6.0", - "version_normalized": "1.6.0.0", + "name": "bshaffer/oauth2-server-php", + "version": "v1.11.1", + "version_normalized": "1.11.1.0", "source": { "type": "git", - "url": "https://github.com/ddeboer/imap.git", - "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede" + "url": "https://github.com/bshaffer/oauth2-server-php.git", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ddeboer/imap/zipball/4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", - "reference": "4d3b31c7cc5eb3cf3a8a0369fabd0d6e3f39cede", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", + "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.8", + "firebase/php-jwt": "~2.2", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^4.0", + "predis/predis": "dev-master", + "thobbs/phpcassa": "dev-master" + }, + "suggest": { + "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", + "firebase/php-jwt": "~2.2 is required to use JWT features", + "mongodb/mongodb": "^1.1 is required to use MongoDB storage", + "predis/predis": "Required to use Redis storage", + "thobbs/phpcassa": "Required to use Cassandra storage" + }, + "time": "2018-12-04T00:29:32+00:00", + "type": "library", + "installation-source": "dist", + "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" + ] + }, + { + "name": "ddeboer/imap", + "version": "1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "ff985d72916267cba2f944e7c9ee654c69893219" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/ff985d72916267cba2f944e7c9ee654c69893219", + "reference": "ff985d72916267cba2f944e7c9ee654c69893219", "shasum": "" }, "require": { @@ -21,13 +81,14 @@ "php": "^7.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.13", - "phpstan/phpstan": "^0.9.1", - "phpstan/phpstan-phpunit": "^0.9.3", - "phpunit/phpunit": "^7.4", + "friendsofphp/php-cs-fixer": "^2.14", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpunit": "^7.5", "zendframework/zend-mail": "^2.10" }, - "time": "2018-12-04T13:35:19+00:00", + "time": "2019-04-15T09:18:52+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -222,34 +283,34 @@ }, { "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "2.11.1", - "version_normalized": "2.11.1.0", + "version": "5.0.5", + "version_normalized": "5.0.5.0", "source": { "type": "git", "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af" + "reference": "27983433aabeccee832573c3c56e6a4855e57745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/4769e942ed0dbbdd7882fc390b119d625463c8af", - "reference": "4769e942ed0dbbdd7882fc390b119d625463c8af", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/27983433aabeccee832573c3c56e6a4855e57745", + "reference": "27983433aabeccee832573c3c56e6a4855e57745", "shasum": "" }, "require": { "ext-mailparse": "*", - "php": "^5.4.0 || ^7.0" + "php": "^7.1" }, "replace": { "exorus/php-mime-mail-parser": "*", "messaged/php-mime-mail-parser": "*" }, "require-dev": { - "phpunit/php-token-stream": "^1.3.0", - "phpunit/phpunit": "^4.0 || ^5.0", - "satooshi/php-coveralls": "0.*", - "squizlabs/php_codesniffer": "2.*" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/php-token-stream": "^3.0", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.4" }, - "time": "2018-04-30T05:55:59+00:00", + "time": "2019-09-23T11:57:58+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -262,12 +323,6 @@ "MIT" ], "authors": [ - { - "name": "bucabay", - "email": "gabe@fijiwebdesign.com", - "homepage": "http://www.fijiwebdesign.com", - "role": "Developer" - }, { "name": "eXorus", "email": "exorus.spam@gmail.com", @@ -291,15 +346,23 @@ "email": "alkne@gmail.com", "homepage": "https://code.google.com/p/php-mime-mail-parser", "role": "Developer" + }, + { + "name": "bucabay", + "email": "gabe@fijiwebdesign.com", + "homepage": "http://www.fijiwebdesign.com", + "role": "Developer" } ], - "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+", + "description": "A fully tested email parser for PHP 7.1+ (mailparse extension wrapper).", "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", "keywords": [ "MimeMailParser", "mail", "mailparse", - "mime" + "mime", + "parser", + "php" ] }, { @@ -383,17 +446,17 @@ }, { "name": "robthree/twofactorauth", - "version": "1.6.5", - "version_normalized": "1.6.5.0", + "version": "1.6.7", + "version_normalized": "1.6.7.0", "source": { "type": "git", "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2" + "reference": "3407c33775391fa8c36f7d766f26c5e59a736374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/f5f58a4c62d0336a0e6175856894a51f3565dad2", - "reference": "f5f58a4c62d0336a0e6175856894a51f3565dad2", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/3407c33775391fa8c36f7d766f26c5e59a736374", + "reference": "3407c33775391fa8c36f7d766f26c5e59a736374", "shasum": "" }, "require": { @@ -402,7 +465,7 @@ "require-dev": { "phpunit/phpunit": "@stable" }, - "time": "2018-06-09T10:09:59+00:00", + "time": "2019-06-21T08:51:04+00:00", "type": "library", "installation-source": "dist", "autoload": { diff --git a/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md index 4635313f..1900cfea 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md +++ b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md @@ -1,5 +1,48 @@ # Change Log +## [1.8.0](https://github.com/ddeboer/imap/tree/1.8.0) (2019-04-15) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.7.2...1.8.0) + +**Implemented enhancements:** + +- Add phpstan-strict-rules, expose PartiInterface::getDescription\(\) [\#409](https://github.com/ddeboer/imap/pull/409) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.7.2](https://github.com/ddeboer/imap/tree/1.7.2) (2019-04-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.7.1...1.7.2) + +**Fixed bugs:** + +- Handle message/rfc822 when content-disposition is missing [\#410](https://github.com/ddeboer/imap/pull/410) ([Daredzik](https://github.com/Daredzik)) + +## [1.7.1](https://github.com/ddeboer/imap/tree/1.7.1) (2019-03-18) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.7.0...1.7.1) + +**Fixed bugs:** + +- Encoding problem with 1.7 [\#405](https://github.com/ddeboer/imap/issues/405) +- imap\_search/imap\_sort: default params must not be passed if unspecified [\#406](https://github.com/ddeboer/imap/pull/406) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.7.0](https://github.com/ddeboer/imap/tree/1.7.0) (2019-03-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.6.0...1.7.0) + +**Implemented enhancements:** + +- Docker and Travis differs in handling new message eols [\#404](https://github.com/ddeboer/imap/pull/404) ([Slamdunk](https://github.com/Slamdunk)) +- Update PHP-CS-Fixer rules [\#403](https://github.com/ddeboer/imap/pull/403) ([Slamdunk](https://github.com/Slamdunk)) +- Add charset for imap\_search or imap\_sort [\#402](https://github.com/ddeboer/imap/pull/402) ([Slamdunk](https://github.com/Slamdunk)) +- PHPStan clean ups [\#400](https://github.com/ddeboer/imap/pull/400) ([Slamdunk](https://github.com/Slamdunk)) +- Adding an undelete\(\) message method [\#386](https://github.com/ddeboer/imap/pull/386) ([C-Duv](https://github.com/C-Duv)) + +**Closed issues:** + +- Convert from GBK \(X-GBK\) to UTF-8 Issue [\#395](https://github.com/ddeboer/imap/issues/395) + +**Merged pull requests:** + +- Add new ResourceCheckFailureException to handle imap\_check\(\) false [\#399](https://github.com/ddeboer/imap/pull/399) ([pyatnitsev](https://github.com/pyatnitsev)) +- Remove GBK -\> X-GBK Alias and add X-GBK -\> GBK [\#396](https://github.com/ddeboer/imap/pull/396) ([pyatnitsev](https://github.com/pyatnitsev)) +- Add Feature Requests to README.md [\#394](https://github.com/ddeboer/imap/pull/394) ([Slamdunk](https://github.com/Slamdunk)) + ## [1.6.0](https://github.com/ddeboer/imap/tree/1.6.0) (2018-12-04) [Full Changelog](https://github.com/ddeboer/imap/compare/1.5.5...1.6.0) diff --git a/data/web/inc/lib/vendor/ddeboer/imap/README.md b/data/web/inc/lib/vendor/ddeboer/imap/README.md index 250f0504..c3191f7e 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/README.md +++ b/data/web/inc/lib/vendor/ddeboer/imap/README.md @@ -14,6 +14,7 @@ This library requires [IMAP](https://secure.php.net/manual/en/book.imap.php), ## Table of Contents +1. [Feature Requests](#feature-requests) 1. [Installation](#installation) 1. [Usage](#usage) 1. [Connect and Authenticate](#connect-and-authenticate) @@ -29,6 +30,10 @@ This library requires [IMAP](https://secure.php.net/manual/en/book.imap.php), 1. [Running the Tests](#running-the-tests) 1. [Running Tests using Docker](#running-tests-using-docker) +## Feature Requests + +[![Feature Requests](https://feathub.com/ddeboer/imap?format=svg)](https://feathub.com/ddeboer/imap) + ## Installation The recommended way to install the IMAP library is through [Composer](https://getcomposer.org): diff --git a/data/web/inc/lib/vendor/ddeboer/imap/composer.json b/data/web/inc/lib/vendor/ddeboer/imap/composer.json index 2286f310..da5fe509 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/composer.json +++ b/data/web/inc/lib/vendor/ddeboer/imap/composer.json @@ -28,10 +28,11 @@ "ext-mbstring": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.13", - "phpstan/phpstan": "^0.9.1", - "phpstan/phpstan-phpunit": "^0.9.3", - "phpunit/phpunit": "^7.4", + "friendsofphp/php-cs-fixer": "^2.14", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpunit": "^7.5", "zendframework/zend-mail": "^2.10" }, "autoload": { diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php index a12ec789..cfca0c55 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php @@ -7,6 +7,7 @@ namespace Ddeboer\Imap; use Ddeboer\Imap\Exception\CreateMailboxException; use Ddeboer\Imap\Exception\DeleteMailboxException; use Ddeboer\Imap\Exception\ImapGetmailboxesException; +use Ddeboer\Imap\Exception\ImapNumMsgException; use Ddeboer\Imap\Exception\InvalidResourceException; use Ddeboer\Imap\Exception\MailboxDoesNotExistException; @@ -46,7 +47,7 @@ final class Connection implements ConnectionInterface public function __construct(ImapResourceInterface $resource, string $server) { $this->resource = $resource; - $this->server = $server; + $this->server = $server; } /** @@ -139,7 +140,13 @@ final class Connection implements ConnectionInterface */ public function count() { - return \imap_num_msg($this->resource->getStream()); + $return = \imap_num_msg($this->resource->getStream()); + + if (false === $return) { + throw new ImapNumMsgException('imap_num_msg failed'); + } + + return $return; } /** @@ -202,13 +209,13 @@ final class Connection implements ConnectionInterface } $this->mailboxNames = []; - $mailboxesInfo = \imap_getmailboxes($this->resource->getStream(), $this->server, '*'); + $mailboxesInfo = \imap_getmailboxes($this->resource->getStream(), $this->server, '*'); if (!\is_array($mailboxesInfo)) { throw new ImapGetmailboxesException('imap_getmailboxes failed'); } foreach ($mailboxesInfo as $mailboxInfo) { - $name = \mb_convert_encoding(\str_replace($this->server, '', $mailboxInfo->name), 'UTF-8', 'UTF7-IMAP'); + $name = \mb_convert_encoding(\str_replace($this->server, '', $mailboxInfo->name), 'UTF-8', 'UTF7-IMAP'); $this->mailboxNames[$name] = $mailboxInfo; } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php index 10a1518e..26c1e586 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AbstractException.php @@ -10,21 +10,21 @@ abstract class AbstractException extends \RuntimeException * @var array */ private static $errorLabels = [ - \E_ERROR => 'E_ERROR', - \E_WARNING => 'E_WARNING', - \E_PARSE => 'E_PARSE', - \E_NOTICE => 'E_NOTICE', - \E_CORE_ERROR => 'E_CORE_ERROR', - \E_CORE_WARNING => 'E_CORE_WARNING', - \E_COMPILE_ERROR => 'E_COMPILE_ERROR', - \E_COMPILE_WARNING => 'E_COMPILE_WARNING', - \E_USER_ERROR => 'E_USER_ERROR', - \E_USER_WARNING => 'E_USER_WARNING', - \E_USER_NOTICE => 'E_USER_NOTICE', - \E_STRICT => 'E_STRICT', - \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', - \E_DEPRECATED => 'E_DEPRECATED', - \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', ]; /** @@ -35,21 +35,21 @@ abstract class AbstractException extends \RuntimeException final public function __construct(string $message, int $code = 0, \Throwable $previous = null) { $errorType = ''; - if (\is_int($code) && isset(self::$errorLabels[$code])) { + if (isset(self::$errorLabels[$code])) { $errorType = \sprintf('[%s] ', self::$errorLabels[$code]); } - $joinString = "\n- "; - $alerts = \imap_alerts(); - $errors = \imap_errors(); + $joinString = "\n- "; + $alerts = \imap_alerts(); + $errors = \imap_errors(); $completeMessage = \sprintf( "%s%s\nimap_alerts (%s):%s\nimap_errors (%s):%s", $errorType, $message, - $alerts ? \count($alerts) : 0, - $alerts ? $joinString . \implode($joinString, $alerts) : '', - $errors ? \count($errors) : 0, - $errors ? $joinString . \implode($joinString, $errors) : '' + false !== $alerts ? \count($alerts) : 0, + false !== $alerts ? $joinString . \implode($joinString, $alerts) : '', + false !== $errors ? \count($errors) : 0, + false !== $errors ? $joinString . \implode($joinString, $errors) : '' ); parent::__construct($completeMessage, $code, $previous); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapFetchbodyException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapFetchbodyException.php new file mode 100644 index 00000000..0e2f30f0 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/ImapFetchbodyException.php @@ -0,0 +1,9 @@ +resource = $resource; - $this->mailbox = $mailbox; + $this->mailbox = $mailbox; } /** @@ -69,13 +69,13 @@ final class ImapResource implements ImapResourceInterface */ private function initMailbox(): void { - if (null === $this->mailbox || $this->isMailboxOpen()) { + if (null === $this->mailbox || self::isMailboxOpen($this->mailbox, $this->resource)) { return; } \imap_reopen($this->resource, $this->mailbox->getFullEncodedName()); - if ($this->isMailboxOpen()) { + if (self::isMailboxOpen($this->mailbox, $this->resource)) { return; } @@ -85,18 +85,18 @@ final class ImapResource implements ImapResourceInterface /** * Check whether the current mailbox is open. * - * @return bool + * @param mixed $resource */ - private function isMailboxOpen(): bool + private static function isMailboxOpen(MailboxInterface $mailbox, $resource): bool { - $currentMailboxName = $this->mailbox->getFullEncodedName(); + $currentMailboxName = $mailbox->getFullEncodedName(); if ($currentMailboxName === self::$lastMailboxUsedCache) { return true; } self::$lastMailboxUsedCache = null; - $check = \imap_check($this->resource); - $return = false !== $check && $check->Mailbox === $currentMailboxName; + $check = \imap_check($resource); + $return = false !== $check && $check->Mailbox === $currentMailboxName; if (true === $return) { self::$lastMailboxUsedCache = $currentMailboxName; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php index 62fcc1d4..27f816b6 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php @@ -5,6 +5,8 @@ declare(strict_types=1); namespace Ddeboer\Imap; use DateTimeInterface; +use Ddeboer\Imap\Exception\ImapNumMsgException; +use Ddeboer\Imap\Exception\ImapStatusException; use Ddeboer\Imap\Exception\InvalidSearchCriteriaException; use Ddeboer\Imap\Exception\MessageCopyException; use Ddeboer\Imap\Exception\MessageMoveException; @@ -41,8 +43,8 @@ final class Mailbox implements MailboxInterface public function __construct(ImapResourceInterface $resource, string $name, \stdClass $info) { $this->resource = new ImapResource($resource->getStream(), $this); - $this->name = $name; - $this->info = $info; + $this->name = $name; + $this->info = $info; } /** @@ -62,7 +64,10 @@ final class Mailbox implements MailboxInterface */ public function getEncodedName(): string { - return \preg_replace('/^{.+}/', '', $this->info->name); + /** @var string $name */ + $name = $this->info->name; + + return (string) \preg_replace('/^{.+}/', '', $name); } /** @@ -102,7 +107,13 @@ final class Mailbox implements MailboxInterface */ public function count() { - return \imap_num_msg($this->resource->getStream()); + $return = \imap_num_msg($this->resource->getStream()); + + if (false === $return) { + throw new ImapNumMsgException('imap_num_msg failed'); + } + + return $return; } /** @@ -114,7 +125,13 @@ final class Mailbox implements MailboxInterface */ public function getStatus(int $flags = null): \stdClass { - return \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); + $return = \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); + + if (false === $return) { + throw new ImapStatusException('imap_status failed'); + } + + return $return; } /** @@ -150,7 +167,7 @@ final class Mailbox implements MailboxInterface * * @return MessageIteratorInterface */ - public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false): MessageIteratorInterface + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface { if (null === $search) { $search = new All(); @@ -162,9 +179,27 @@ final class Mailbox implements MailboxInterface \imap_errors(); if (null !== $sortCriteria) { - $messageNumbers = \imap_sort($this->resource->getStream(), $sortCriteria, $descending ? 1 : 0, \SE_UID, $query); + $params = [ + $this->resource->getStream(), + $sortCriteria, + $descending ? 1 : 0, + \SE_UID, + $query, + ]; + if (null !== $charset) { + $params[] = $charset; + } + $messageNumbers = \imap_sort(...$params); } else { - $messageNumbers = \imap_search($this->resource->getStream(), $query, \SE_UID); + $params = [ + $this->resource->getStream(), + $query, + \SE_UID, + ]; + if (null !== $charset) { + $params[] = $charset; + } + $messageNumbers = \imap_search(...$params); } if (false === $messageNumbers) { if (false !== \imap_last_error()) { @@ -189,15 +224,15 @@ final class Mailbox implements MailboxInterface { \imap_errors(); - $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, FT_UID); - if (empty($overview)) { + $overview = \imap_fetch_overview($this->resource->getStream(), $sequence, \FT_UID); + if (\is_array($overview) && [] !== $overview) { + $messageNumbers = \array_column($overview, 'uid'); + } else { if (false !== \imap_last_error()) { throw new InvalidSearchCriteriaException(\sprintf('Invalid sequence [%s]', $sequence)); } $messageNumbers = []; - } else { - $messageNumbers = \array_column($overview, 'uid'); } return new MessageIterator($this->resource, $messageNumbers); @@ -258,8 +293,9 @@ final class Mailbox implements MailboxInterface */ public function getThread(): array { - \set_error_handler(function () {}); + \set_error_handler(static function () {}); + /** @var array|false $tree */ $tree = \imap_thread($this->resource->getStream()); \restore_error_handler(); @@ -314,6 +350,6 @@ final class Mailbox implements MailboxInterface $messageIds = \implode(',', $messageIds); } - return (string) $messageIds; + return $messageIds; } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php index 59477a41..9491a734 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php @@ -83,7 +83,7 @@ interface MailboxInterface extends \Countable, \IteratorAggregate * * @return MessageIteratorInterface */ - public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false): MessageIteratorInterface; + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false, string $charset = null): MessageIteratorInterface; /** * Get message iterator for a sequence. diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php index c65fa313..e5c56c78 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php @@ -4,12 +4,14 @@ declare(strict_types=1); namespace Ddeboer\Imap; +use Ddeboer\Imap\Exception\ImapFetchheaderException; use Ddeboer\Imap\Exception\InvalidHeadersException; use Ddeboer\Imap\Exception\MessageCopyException; use Ddeboer\Imap\Exception\MessageDeleteException; use Ddeboer\Imap\Exception\MessageDoesNotExistException; use Ddeboer\Imap\Exception\MessageMoveException; use Ddeboer\Imap\Exception\MessageStructureException; +use Ddeboer\Imap\Exception\MessageUndeleteException; /** * An IMAP message (e-mail). @@ -21,6 +23,11 @@ final class Message extends Message\AbstractMessage implements MessageInterface */ private $messageNumberVerified = false; + /** + * @var int + */ + private $imapMsgNo = 0; + /** * @var bool */ @@ -65,8 +72,8 @@ final class Message extends Message\AbstractMessage implements MessageInterface $messageNumber = $this->getNumber(); $errorMessage = null; - $errorNumber = 0; - \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber) { $errorMessage = $message; $errorNumber = $nr; }); @@ -104,6 +111,8 @@ final class Message extends Message\AbstractMessage implements MessageInterface $msgno = \imap_msgno($this->resource->getStream(), $messageNumber); if (\is_numeric($msgno) && $msgno > 0) { + $this->imapMsgNo = $msgno; + return; } @@ -113,6 +122,14 @@ final class Message extends Message\AbstractMessage implements MessageInterface )); } + private function getMsgNo(): int + { + // Triggers assertMessageExists() + $this->getNumber(); + + return $this->imapMsgNo; + } + /** * Get raw message headers. * @@ -121,7 +138,13 @@ final class Message extends Message\AbstractMessage implements MessageInterface public function getRawHeaders(): string { if (null === $this->rawHeaders) { - $this->rawHeaders = \imap_fetchheader($this->resource->getStream(), $this->getNumber(), \FT_UID); + $rawHeaders = \imap_fetchheader($this->resource->getStream(), $this->getNumber(), \FT_UID); + + if (false === $rawHeaders) { + throw new ImapFetchheaderException('imap_fetchheader failed'); + } + + $this->rawHeaders = $rawHeaders; } return $this->rawHeaders; @@ -152,7 +175,7 @@ final class Message extends Message\AbstractMessage implements MessageInterface // imap_headerinfo is much faster than imap_fetchheader // imap_headerinfo returns only a subset of all mail headers, // but it does include the message flags. - $headers = \imap_headerinfo($this->resource->getStream(), \imap_msgno($this->resource->getStream(), $this->getNumber())); + $headers = \imap_headerinfo($this->resource->getStream(), $this->getMsgNo()); if (false === $headers) { // @see https://github.com/ddeboer/imap/issues/358 throw new InvalidHeadersException(\sprintf('Message "%s" has invalid headers', $this->getNumber())); @@ -314,6 +337,20 @@ final class Message extends Message\AbstractMessage implements MessageInterface } } + /** + * Undelete message. + * + * @throws MessageUndeleteException + */ + public function undelete(): void + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to false on header + $this->clearHeaders(); + if (!\imap_undelete($this->resource->getStream(), $this->getNumber(), \FT_UID)) { + throw new MessageUndeleteException(\sprintf('Message "%s" cannot be undeleted', $this->getNumber())); + } + } + /** * Set Flag Message. * diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php index a7fdb02e..a9671dd4 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php @@ -111,6 +111,7 @@ abstract class AbstractMessage extends AbstractPart */ final public function getDate(): ?\DateTimeImmutable { + /** @var null|string $dateHeader */ $dateHeader = $this->getHeaders()->get('date'); if (null === $dateHeader) { return null; @@ -118,9 +119,9 @@ abstract class AbstractMessage extends AbstractPart $alteredValue = $dateHeader; $alteredValue = \str_replace(',', '', $alteredValue); - $alteredValue = \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); - $alteredValue = \preg_replace('/ +\(.*\)/', '', $alteredValue); - $alteredValue = \preg_replace('/\bUT\b/', 'UTC', $alteredValue); + $alteredValue = (string) \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); + $alteredValue = (string) \preg_replace('/ +\(.*\)/', '', $alteredValue); + $alteredValue = (string) \preg_replace('/\bUT\b/', 'UTC', $alteredValue); if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) { $alteredValue .= ' +0000'; } @@ -230,29 +231,32 @@ abstract class AbstractMessage extends AbstractPart final public function getAttachments(): array { if (null === $this->attachments) { - static $gatherAttachments; - if (null === $gatherAttachments) { - $gatherAttachments = static function (PartInterface $part) use (&$gatherAttachments): array { - $attachments = []; - foreach ($part->getParts() as $childPart) { - if ($childPart instanceof Attachment) { - $attachments[] = $childPart; - } - if ($childPart->hasChildren()) { - $attachments = \array_merge($attachments, $gatherAttachments($childPart)); - } - } - - return $attachments; - }; - } - - $this->attachments = $gatherAttachments($this); + $this->attachments = self::gatherAttachments($this); } return $this->attachments; } + /** + * @param PartInterface $part + * + * @return array + */ + private static function gatherAttachments(PartInterface $part): array + { + $attachments = []; + foreach ($part->getParts() as $childPart) { + if ($childPart instanceof Attachment) { + $attachments[] = $childPart; + } + if ($childPart->hasChildren()) { + $attachments = \array_merge($attachments, self::gatherAttachments($childPart)); + } + } + + return $attachments; + } + /** * Does this message have attachments? * @@ -264,7 +268,7 @@ abstract class AbstractMessage extends AbstractPart } /** - * @param array $addresses Addesses + * @param \stdClass[] $addresses * * @return array */ diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php index 4be6738c..6d8d6b1e 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Ddeboer\Imap\Message; +use Ddeboer\Imap\Exception\ImapFetchbodyException; use Ddeboer\Imap\Exception\UnexpectedEncodingException; use Ddeboer\Imap\ImapResourceInterface; use Ddeboer\Imap\Message; @@ -68,6 +69,11 @@ abstract class AbstractPart implements PartInterface */ private $disposition; + /** + * @var null|string + */ + private $description; + /** * @var null|string */ @@ -97,25 +103,25 @@ abstract class AbstractPart implements PartInterface * @var array */ private static $typesMap = [ - \TYPETEXT => self::TYPE_TEXT, - \TYPEMULTIPART => self::TYPE_MULTIPART, - \TYPEMESSAGE => self::TYPE_MESSAGE, + \TYPETEXT => self::TYPE_TEXT, + \TYPEMULTIPART => self::TYPE_MULTIPART, + \TYPEMESSAGE => self::TYPE_MESSAGE, \TYPEAPPLICATION => self::TYPE_APPLICATION, - \TYPEAUDIO => self::TYPE_AUDIO, - \TYPEIMAGE => self::TYPE_IMAGE, - \TYPEVIDEO => self::TYPE_VIDEO, - \TYPEMODEL => self::TYPE_MODEL, - \TYPEOTHER => self::TYPE_OTHER, + \TYPEAUDIO => self::TYPE_AUDIO, + \TYPEIMAGE => self::TYPE_IMAGE, + \TYPEVIDEO => self::TYPE_VIDEO, + \TYPEMODEL => self::TYPE_MODEL, + \TYPEOTHER => self::TYPE_OTHER, ]; /** * @var array */ private static $encodingsMap = [ - \ENC7BIT => self::ENCODING_7BIT, - \ENC8BIT => self::ENCODING_8BIT, - \ENCBINARY => self::ENCODING_BINARY, - \ENCBASE64 => self::ENCODING_BASE64, + \ENC7BIT => self::ENCODING_7BIT, + \ENC8BIT => self::ENCODING_8BIT, + \ENCBINARY => self::ENCODING_BINARY, + \ENCBASE64 => self::ENCODING_BASE64, \ENCQUOTEDPRINTABLE => self::ENCODING_QUOTED_PRINTABLE, ]; @@ -123,9 +129,9 @@ abstract class AbstractPart implements PartInterface * @var array */ private static $attachmentKeys = [ - 'name' => true, - 'filename' => true, - 'name*' => true, + 'name' => true, + 'filename' => true, + 'name*' => true, 'filename*' => true, ]; @@ -143,9 +149,9 @@ abstract class AbstractPart implements PartInterface string $partNumber, \stdClass $structure ) { - $this->resource = $resource; + $this->resource = $resource; $this->messageNumber = $messageNumber; - $this->partNumber = $partNumber; + $this->partNumber = $partNumber; $this->setStructure($structure); } @@ -269,6 +275,18 @@ abstract class AbstractPart implements PartInterface return $this->disposition; } + /** + * Part description. + * + * @return null|string + */ + final public function getDescription(): ?string + { + $this->lazyParseStructure(); + + return $this->description; + } + /** * Part bytes. * @@ -341,7 +359,7 @@ abstract class AbstractPart implements PartInterface $content = $this->getContent(); if (self::ENCODING_BASE64 === $this->getEncoding()) { - $content = \base64_decode($content); + $content = \base64_decode($content, false); } elseif (self::ENCODING_QUOTED_PRINTABLE === $this->getEncoding()) { $content = \quoted_printable_decode($content); } @@ -371,12 +389,18 @@ abstract class AbstractPart implements PartInterface */ final protected function doGetContent(string $partNumber): string { - return \imap_fetchbody( + $return = \imap_fetchbody( $this->resource->getStream(), $this->getNumber(), $partNumber, \FT_UID | \FT_PEEK ); + + if (false === $return) { + throw new ImapFetchbodyException('imap_fetchbody failed'); + } + + return $return; } /** @@ -406,7 +430,7 @@ abstract class AbstractPart implements PartInterface /** * Get current child part. * - * @return mixed + * @return \RecursiveIterator */ final public function getChildren() { @@ -483,12 +507,16 @@ abstract class AbstractPart implements PartInterface // In our context, \ENCOTHER is as useful as an uknown encoding $this->encoding = self::$encodingsMap[$this->structure->encoding] ?? self::ENCODING_UNKNOWN; - $this->subtype = $this->structure->subtype; + $this->subtype = $this->structure->subtype; - foreach (['disposition', 'bytes', 'description'] as $optional) { - if (isset($this->structure->{$optional})) { - $this->{$optional} = $this->structure->{$optional}; - } + if (isset($this->structure->bytes)) { + $this->bytes = $this->structure->bytes; + } + if ($this->structure->ifdisposition) { + $this->disposition = $this->structure->disposition; + } + if ($this->structure->ifdescription) { + $this->description = $this->structure->description; } $this->parameters = new Parameters(); @@ -573,6 +601,10 @@ abstract class AbstractPart implements PartInterface } */ + if (self::SUBTYPE_RFC822 === \strtoupper($part->subtype)) { + return true; + } + return false; } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php index 35655fdf..779791b8 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php @@ -25,11 +25,16 @@ final class Attachment extends AbstractPart implements AttachmentInterface /** * Get attachment file size. * - * @return int Number of bytes + * @return null|int Number of bytes */ public function getSize() { - return $this->getParameters()->get('size'); + $size = $this->getParameters()->get('size'); + if (\is_numeric($size)) { + $size = (int) $size; + } + + return $size; } /** diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php index 595971b9..714dd33e 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php @@ -19,7 +19,7 @@ interface AttachmentInterface extends PartInterface /** * Get attachment file size. * - * @return int Number of bytes + * @return null|int Number of bytes */ public function getSize(); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php index 3f3788a4..6c24704e 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php @@ -36,9 +36,9 @@ final class EmailAddress */ public function __construct(string $mailbox, string $hostname = null, string $name = null) { - $this->mailbox = $mailbox; + $this->mailbox = $mailbox; $this->hostname = $hostname; - $this->name = $name; + $this->name = $name; if (null !== $hostname) { $this->address = $mailbox . '@' . $hostname; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php index 1dfb8fd6..e05d3d25 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php @@ -43,7 +43,7 @@ final class EmbeddedMessage extends AbstractMessage implements EmbeddedMessageIn public function getRawHeaders(): string { if (null === $this->rawHeaders) { - $rawHeaders = \explode("\r\n\r\n", $this->getRawMessage(), 2); + $rawHeaders = \explode("\r\n\r\n", $this->getRawMessage(), 2); $this->rawHeaders = \current($rawHeaders); } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php index f2848663..1c7b148c 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php @@ -31,7 +31,7 @@ final class Headers extends Parameters * * @param string $key * - * @return null|string + * @return mixed */ public function get(string $key) { @@ -58,9 +58,10 @@ final class Headers extends Parameters case 'reply_to': case 'sender': case 'return_path': + /** @var \stdClass $address */ foreach ($value as $address) { if (isset($address->mailbox)) { - $address->host = $address->host ?? null; + $address->host = $address->host ?? null; $address->personal = isset($address->personal) ? $this->decode($address->personal) : null; } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php index bb2a66cb..25e04a10 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php @@ -10,7 +10,7 @@ class Parameters extends \ArrayIterator * @var array */ private static $attachmentCustomKeys = [ - 'name*' => 'name', + 'name*' => 'name', 'filename*' => 'filename', ]; @@ -34,7 +34,7 @@ class Parameters extends \ArrayIterator if (isset(self::$attachmentCustomKeys[$key])) { $key = self::$attachmentCustomKeys[$key]; } - $value = $this->decode($parameter->value); + $value = $this->decode($parameter->value); $this[$key] = $value; } } @@ -71,8 +71,8 @@ class Parameters extends \ArrayIterator } // RFC2231 if (1 === \preg_match('/^(?[^\']+)\'[^\']*?\'(?.+)$/', $text, $matches)) { - $hasInvalidChars = \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']); - $hasEscapedChars = \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']); + $hasInvalidChars = 1 === \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']); + $hasEscapedChars = 1 === \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']); if (!$hasInvalidChars && $hasEscapedChars) { $text = Transcoder::decode(\urldecode($matches['urltext']), $matches['encoding']); } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php index d5f63699..e1ea0f2b 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php @@ -9,26 +9,27 @@ namespace Ddeboer\Imap\Message; */ interface PartInterface extends \RecursiveIterator { - const TYPE_TEXT = 'text'; - const TYPE_MULTIPART = 'multipart'; - const TYPE_MESSAGE = 'message'; - const TYPE_APPLICATION = 'application'; - const TYPE_AUDIO = 'audio'; - const TYPE_IMAGE = 'image'; - const TYPE_VIDEO = 'video'; - const TYPE_MODEL = 'model'; - const TYPE_OTHER = 'other'; - const TYPE_UNKNOWN = 'unknown'; + public const TYPE_TEXT = 'text'; + public const TYPE_MULTIPART = 'multipart'; + public const TYPE_MESSAGE = 'message'; + public const TYPE_APPLICATION = 'application'; + public const TYPE_AUDIO = 'audio'; + public const TYPE_IMAGE = 'image'; + public const TYPE_VIDEO = 'video'; + public const TYPE_MODEL = 'model'; + public const TYPE_OTHER = 'other'; + public const TYPE_UNKNOWN = 'unknown'; - const ENCODING_7BIT = '7bit'; - const ENCODING_8BIT = '8bit'; - const ENCODING_BINARY = 'binary'; - const ENCODING_BASE64 = 'base64'; - const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; - const ENCODING_UNKNOWN = 'unknown'; + public const ENCODING_7BIT = '7bit'; + public const ENCODING_8BIT = '8bit'; + public const ENCODING_BINARY = 'binary'; + public const ENCODING_BASE64 = 'base64'; + public const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + public const ENCODING_UNKNOWN = 'unknown'; - const SUBTYPE_PLAIN = 'PLAIN'; - const SUBTYPE_HTML = 'HTML'; + public const SUBTYPE_PLAIN = 'PLAIN'; + public const SUBTYPE_HTML = 'HTML'; + public const SUBTYPE_RFC822 = 'RFC822'; /** * Get message number (from headers). @@ -72,6 +73,13 @@ interface PartInterface extends \RecursiveIterator */ public function getDisposition(): ?string; + /** + * Part description. + * + * @return null|string + */ + public function getDescription(): ?string; + /** * Part bytes. * diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php index 28daec13..ec97f952 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php @@ -17,247 +17,247 @@ final class Transcoder * @see https://msdn.microsoft.com/en-us/library/cc194829.aspx */ private static $charsetAliases = [ - '128' => 'Shift_JIS', - '129' => 'EUC-KR', - '134' => 'GB2312', - '136' => 'Big5', - '161' => 'windows-1253', - '162' => 'windows-1254', - '177' => 'windows-1255', - '178' => 'windows-1256', - '186' => 'windows-1257', - '204' => 'windows-1251', - '222' => 'windows-874', - '238' => 'windows-1250', - '5601' => 'EUC-KR', - '646' => 'us-ascii', - '850' => 'IBM850', - '852' => 'IBM852', - '855' => 'IBM855', - '857' => 'IBM857', - '862' => 'IBM862', - '864' => 'IBM864', - '864i' => 'IBM864i', - '866' => 'IBM866', - 'ansi-1251' => 'windows-1251', - 'ansi_x3.4-1968' => 'us-ascii', - 'arabic' => 'ISO-8859-6', - 'ascii' => 'us-ascii', - 'asmo-708' => 'ISO-8859-6', - 'big5-hkscs' => 'Big5', - 'chinese' => 'GB2312', - 'cn-big5' => 'Big5', - 'cns11643' => 'x-euc-tw', - 'cp-866' => 'IBM866', - 'cp1250' => 'windows-1250', - 'cp1251' => 'windows-1251', - 'cp1252' => 'windows-1252', - 'cp1253' => 'windows-1253', - 'cp1254' => 'windows-1254', - 'cp1255' => 'windows-1255', - 'cp1256' => 'windows-1256', - 'cp1257' => 'windows-1257', - 'cp1258' => 'windows-1258', - 'cp819' => 'ISO-8859-1', - 'cp850' => 'IBM850', - 'cp852' => 'IBM852', - 'cp855' => 'IBM855', - 'cp857' => 'IBM857', - 'cp862' => 'IBM862', - 'cp864' => 'IBM864', - 'cp864i' => 'IBM864i', - 'cp866' => 'IBM866', - 'cp932' => 'Shift_JIS', - 'csbig5' => 'Big5', - 'cseucjpkdfmtjapanese' => 'EUC-JP', - 'cseuckr' => 'EUC-KR', - 'cseucpkdfmtjapanese' => 'EUC-JP', - 'csgb2312' => 'GB2312', - 'csibm850' => 'IBM850', - 'csibm852' => 'IBM852', - 'csibm855' => 'IBM855', - 'csibm857' => 'IBM857', - 'csibm862' => 'IBM862', - 'csibm864' => 'IBM864', - 'csibm864i' => 'IBM864i', - 'csibm866' => 'IBM866', - 'csiso103t618bit' => 'T.61-8bit', - 'csiso111ecmacyrillic' => 'ISO-IR-111', - 'csiso2022jp' => 'ISO-2022-JP', - 'csiso2022jp2' => 'ISO-2022-JP', - 'csiso2022kr' => 'ISO-2022-KR', - 'csiso58gb231280' => 'GB2312', - 'csiso88596e' => 'ISO-8859-6-E', - 'csiso88596i' => 'ISO-8859-6-I', - 'csiso88598e' => 'ISO-8859-8-E', - 'csiso88598i' => 'ISO-8859-8-I', - 'csisolatin1' => 'ISO-8859-1', - 'csisolatin2' => 'ISO-8859-2', - 'csisolatin3' => 'ISO-8859-3', - 'csisolatin4' => 'ISO-8859-4', - 'csisolatin5' => 'ISO-8859-9', - 'csisolatin6' => 'ISO-8859-10', - 'csisolatin9' => 'ISO-8859-15', - 'csisolatinarabic' => 'ISO-8859-6', - 'csisolatincyrillic' => 'ISO-8859-5', - 'csisolatingreek' => 'ISO-8859-7', - 'csisolatinhebrew' => 'ISO-8859-8', - 'cskoi8r' => 'KOI8-R', - 'csksc56011987' => 'EUC-KR', - 'csmacintosh' => 'x-mac-roman', - 'csshiftjis' => 'Shift_JIS', - 'csueckr' => 'EUC-KR', - 'csunicode' => 'UTF-16BE', - 'csunicode11' => 'UTF-16BE', - 'csunicode11utf7' => 'UTF-7', - 'csunicodeascii' => 'UTF-16BE', - 'csunicodelatin1' => 'UTF-16BE', - 'csviqr' => 'VIQR', - 'csviscii' => 'VISCII', - 'cyrillic' => 'ISO-8859-5', - 'dos-874' => 'windows-874', - 'ecma-114' => 'ISO-8859-6', - 'ecma-118' => 'ISO-8859-7', - 'ecma-cyrillic' => 'ISO-IR-111', - 'elot_928' => 'ISO-8859-7', - 'gb_2312' => 'GB2312', - 'gb_2312-80' => 'GB2312', - 'gbk' => 'x-gbk', - 'greek' => 'ISO-8859-7', - 'greek8' => 'ISO-8859-7', - 'hebrew' => 'ISO-8859-8', - 'ibm-864' => 'IBM864', - 'ibm-864i' => 'IBM864i', - 'ibm819' => 'ISO-8859-1', - 'ibm874' => 'windows-874', - 'iso-10646' => 'UTF-16BE', - 'iso-10646-j-1' => 'UTF-16BE', - 'iso-10646-ucs-2' => 'UTF-16BE', - 'iso-10646-ucs-4' => 'UTF-32BE', - 'iso-10646-ucs-basic' => 'UTF-16BE', - 'iso-10646-unicode-latin1' => 'UTF-16BE', - 'iso-2022-cn-ext' => 'ISO-2022-CN', - 'iso-2022-jp-2' => 'ISO-2022-JP', - 'iso-8859-8i' => 'ISO-8859-8-I', - 'iso-ir-100' => 'ISO-8859-1', - 'iso-ir-101' => 'ISO-8859-2', - 'iso-ir-103' => 'T.61-8bit', - 'iso-ir-109' => 'ISO-8859-3', - 'iso-ir-110' => 'ISO-8859-4', - 'iso-ir-126' => 'ISO-8859-7', - 'iso-ir-127' => 'ISO-8859-6', - 'iso-ir-138' => 'ISO-8859-8', - 'iso-ir-144' => 'ISO-8859-5', - 'iso-ir-148' => 'ISO-8859-9', - 'iso-ir-149' => 'EUC-KR', - 'iso-ir-157' => 'ISO-8859-10', - 'iso-ir-58' => 'GB2312', - 'iso8859-1' => 'ISO-8859-1', - 'iso8859-10' => 'ISO-8859-10', - 'iso8859-11' => 'ISO-8859-11', - 'iso8859-13' => 'ISO-8859-13', - 'iso8859-14' => 'ISO-8859-14', - 'iso8859-15' => 'ISO-8859-15', - 'iso8859-2' => 'ISO-8859-2', - 'iso8859-3' => 'ISO-8859-3', - 'iso8859-4' => 'ISO-8859-4', - 'iso8859-5' => 'ISO-8859-5', - 'iso8859-6' => 'ISO-8859-6', - 'iso8859-7' => 'ISO-8859-7', - 'iso8859-8' => 'ISO-8859-8', - 'iso8859-9' => 'ISO-8859-9', - 'iso88591' => 'ISO-8859-1', - 'iso885910' => 'ISO-8859-10', - 'iso885911' => 'ISO-8859-11', - 'iso885912' => 'ISO-8859-12', - 'iso885913' => 'ISO-8859-13', - 'iso885914' => 'ISO-8859-14', - 'iso885915' => 'ISO-8859-15', - 'iso88592' => 'ISO-8859-2', - 'iso88593' => 'ISO-8859-3', - 'iso88594' => 'ISO-8859-4', - 'iso88595' => 'ISO-8859-5', - 'iso88596' => 'ISO-8859-6', - 'iso88597' => 'ISO-8859-7', - 'iso88598' => 'ISO-8859-8', - 'iso88599' => 'ISO-8859-9', - 'iso_8859-1' => 'ISO-8859-1', - 'iso_8859-15' => 'ISO-8859-15', - 'iso_8859-1:1987' => 'ISO-8859-1', - 'iso_8859-2' => 'ISO-8859-2', - 'iso_8859-2:1987' => 'ISO-8859-2', - 'iso_8859-3' => 'ISO-8859-3', - 'iso_8859-3:1988' => 'ISO-8859-3', - 'iso_8859-4' => 'ISO-8859-4', - 'iso_8859-4:1988' => 'ISO-8859-4', - 'iso_8859-5' => 'ISO-8859-5', - 'iso_8859-5:1988' => 'ISO-8859-5', - 'iso_8859-6' => 'ISO-8859-6', - 'iso_8859-6:1987' => 'ISO-8859-6', - 'iso_8859-7' => 'ISO-8859-7', - 'iso_8859-7:1987' => 'ISO-8859-7', - 'iso_8859-8' => 'ISO-8859-8', - 'iso_8859-8:1988' => 'ISO-8859-8', - 'iso_8859-9' => 'ISO-8859-9', - 'iso_8859-9:1989' => 'ISO-8859-9', - 'koi' => 'KOI8-R', - 'koi8' => 'KOI8-R', - 'koi8-ru' => 'KOI8-U', - 'koi8_r' => 'KOI8-R', - 'korean' => 'EUC-KR', - 'ks_c_5601-1987' => 'EUC-KR', - 'ks_c_5601-1989' => 'EUC-KR', - 'ksc5601' => 'EUC-KR', - 'ksc_5601' => 'EUC-KR', - 'l1' => 'ISO-8859-1', - 'l2' => 'ISO-8859-2', - 'l3' => 'ISO-8859-3', - 'l4' => 'ISO-8859-4', - 'l5' => 'ISO-8859-9', - 'l6' => 'ISO-8859-10', - 'l9' => 'ISO-8859-15', - 'latin1' => 'ISO-8859-1', - 'latin2' => 'ISO-8859-2', - 'latin3' => 'ISO-8859-3', - 'latin4' => 'ISO-8859-4', - 'latin5' => 'ISO-8859-9', - 'latin6' => 'ISO-8859-10', - 'logical' => 'ISO-8859-8-I', - 'mac' => 'x-mac-roman', - 'macintosh' => 'x-mac-roman', - 'ms932' => 'Shift_JIS', - 'ms_kanji' => 'Shift_JIS', - 'shift-jis' => 'Shift_JIS', - 'sjis' => 'Shift_JIS', - 'sun_eu_greek' => 'ISO-8859-7', - 't.61' => 'T.61-8bit', - 'tis620' => 'TIS-620', - 'unicode-1-1-utf-7' => 'UTF-7', - 'unicode-1-1-utf-8' => 'UTF-8', - 'unicode-2-0-utf-7' => 'UTF-7', - 'visual' => 'ISO-8859-8', - 'windows-31j' => 'Shift_JIS', - 'windows-949' => 'EUC-KR', - 'x-cp1250' => 'windows-1250', - 'x-cp1251' => 'windows-1251', - 'x-cp1252' => 'windows-1252', - 'x-cp1253' => 'windows-1253', - 'x-cp1254' => 'windows-1254', - 'x-cp1255' => 'windows-1255', - 'x-cp1256' => 'windows-1256', - 'x-cp1257' => 'windows-1257', - 'x-cp1258' => 'windows-1258', - 'x-euc-jp' => 'EUC-JP', - 'x-iso-10646-ucs-2-be' => 'UTF-16BE', - 'x-iso-10646-ucs-2-le' => 'UTF-16LE', - 'x-iso-10646-ucs-4-be' => 'UTF-32BE', - 'x-iso-10646-ucs-4-le' => 'UTF-32LE', - 'x-sjis' => 'Shift_JIS', - 'x-unicode-2-0-utf-7' => 'UTF-7', - 'x-x-big5' => 'Big5', - 'zh_cn.euc' => 'GB2312', - 'zh_tw-big5' => 'Big5', - 'zh_tw-euc' => 'x-euc-tw', + '128' => 'Shift_JIS', + '129' => 'EUC-KR', + '134' => 'GB2312', + '136' => 'Big5', + '161' => 'windows-1253', + '162' => 'windows-1254', + '177' => 'windows-1255', + '178' => 'windows-1256', + '186' => 'windows-1257', + '204' => 'windows-1251', + '222' => 'windows-874', + '238' => 'windows-1250', + '5601' => 'EUC-KR', + '646' => 'us-ascii', + '850' => 'IBM850', + '852' => 'IBM852', + '855' => 'IBM855', + '857' => 'IBM857', + '862' => 'IBM862', + '864' => 'IBM864', + '864i' => 'IBM864i', + '866' => 'IBM866', + 'ansi-1251' => 'windows-1251', + 'ansi_x3.4-1968' => 'us-ascii', + 'arabic' => 'ISO-8859-6', + 'ascii' => 'us-ascii', + 'asmo-708' => 'ISO-8859-6', + 'big5-hkscs' => 'Big5', + 'chinese' => 'GB2312', + 'cn-big5' => 'Big5', + 'cns11643' => 'x-euc-tw', + 'cp-866' => 'IBM866', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'cp819' => 'ISO-8859-1', + 'cp850' => 'IBM850', + 'cp852' => 'IBM852', + 'cp855' => 'IBM855', + 'cp857' => 'IBM857', + 'cp862' => 'IBM862', + 'cp864' => 'IBM864', + 'cp864i' => 'IBM864i', + 'cp866' => 'IBM866', + 'cp932' => 'Shift_JIS', + 'csbig5' => 'Big5', + 'cseucjpkdfmtjapanese' => 'EUC-JP', + 'cseuckr' => 'EUC-KR', + 'cseucpkdfmtjapanese' => 'EUC-JP', + 'csgb2312' => 'GB2312', + 'csibm850' => 'IBM850', + 'csibm852' => 'IBM852', + 'csibm855' => 'IBM855', + 'csibm857' => 'IBM857', + 'csibm862' => 'IBM862', + 'csibm864' => 'IBM864', + 'csibm864i' => 'IBM864i', + 'csibm866' => 'IBM866', + 'csiso103t618bit' => 'T.61-8bit', + 'csiso111ecmacyrillic' => 'ISO-IR-111', + 'csiso2022jp' => 'ISO-2022-JP', + 'csiso2022jp2' => 'ISO-2022-JP', + 'csiso2022kr' => 'ISO-2022-KR', + 'csiso58gb231280' => 'GB2312', + 'csiso88596e' => 'ISO-8859-6-E', + 'csiso88596i' => 'ISO-8859-6-I', + 'csiso88598e' => 'ISO-8859-8-E', + 'csiso88598i' => 'ISO-8859-8-I', + 'csisolatin1' => 'ISO-8859-1', + 'csisolatin2' => 'ISO-8859-2', + 'csisolatin3' => 'ISO-8859-3', + 'csisolatin4' => 'ISO-8859-4', + 'csisolatin5' => 'ISO-8859-9', + 'csisolatin6' => 'ISO-8859-10', + 'csisolatin9' => 'ISO-8859-15', + 'csisolatinarabic' => 'ISO-8859-6', + 'csisolatincyrillic' => 'ISO-8859-5', + 'csisolatingreek' => 'ISO-8859-7', + 'csisolatinhebrew' => 'ISO-8859-8', + 'cskoi8r' => 'KOI8-R', + 'csksc56011987' => 'EUC-KR', + 'csmacintosh' => 'x-mac-roman', + 'csshiftjis' => 'Shift_JIS', + 'csueckr' => 'EUC-KR', + 'csunicode' => 'UTF-16BE', + 'csunicode11' => 'UTF-16BE', + 'csunicode11utf7' => 'UTF-7', + 'csunicodeascii' => 'UTF-16BE', + 'csunicodelatin1' => 'UTF-16BE', + 'csviqr' => 'VIQR', + 'csviscii' => 'VISCII', + 'cyrillic' => 'ISO-8859-5', + 'dos-874' => 'windows-874', + 'ecma-114' => 'ISO-8859-6', + 'ecma-118' => 'ISO-8859-7', + 'ecma-cyrillic' => 'ISO-IR-111', + 'elot_928' => 'ISO-8859-7', + 'gb_2312' => 'GB2312', + 'gb_2312-80' => 'GB2312', + 'greek' => 'ISO-8859-7', + 'greek8' => 'ISO-8859-7', + 'hebrew' => 'ISO-8859-8', + 'ibm-864' => 'IBM864', + 'ibm-864i' => 'IBM864i', + 'ibm819' => 'ISO-8859-1', + 'ibm874' => 'windows-874', + 'iso-10646' => 'UTF-16BE', + 'iso-10646-j-1' => 'UTF-16BE', + 'iso-10646-ucs-2' => 'UTF-16BE', + 'iso-10646-ucs-4' => 'UTF-32BE', + 'iso-10646-ucs-basic' => 'UTF-16BE', + 'iso-10646-unicode-latin1' => 'UTF-16BE', + 'iso-2022-cn-ext' => 'ISO-2022-CN', + 'iso-2022-jp-2' => 'ISO-2022-JP', + 'iso-8859-8i' => 'ISO-8859-8-I', + 'iso-ir-100' => 'ISO-8859-1', + 'iso-ir-101' => 'ISO-8859-2', + 'iso-ir-103' => 'T.61-8bit', + 'iso-ir-109' => 'ISO-8859-3', + 'iso-ir-110' => 'ISO-8859-4', + 'iso-ir-126' => 'ISO-8859-7', + 'iso-ir-127' => 'ISO-8859-6', + 'iso-ir-138' => 'ISO-8859-8', + 'iso-ir-144' => 'ISO-8859-5', + 'iso-ir-148' => 'ISO-8859-9', + 'iso-ir-149' => 'EUC-KR', + 'iso-ir-157' => 'ISO-8859-10', + 'iso-ir-58' => 'GB2312', + 'iso8859-1' => 'ISO-8859-1', + 'iso8859-10' => 'ISO-8859-10', + 'iso8859-11' => 'ISO-8859-11', + 'iso8859-13' => 'ISO-8859-13', + 'iso8859-14' => 'ISO-8859-14', + 'iso8859-15' => 'ISO-8859-15', + 'iso8859-2' => 'ISO-8859-2', + 'iso8859-3' => 'ISO-8859-3', + 'iso8859-4' => 'ISO-8859-4', + 'iso8859-5' => 'ISO-8859-5', + 'iso8859-6' => 'ISO-8859-6', + 'iso8859-7' => 'ISO-8859-7', + 'iso8859-8' => 'ISO-8859-8', + 'iso8859-9' => 'ISO-8859-9', + 'iso88591' => 'ISO-8859-1', + 'iso885910' => 'ISO-8859-10', + 'iso885911' => 'ISO-8859-11', + 'iso885912' => 'ISO-8859-12', + 'iso885913' => 'ISO-8859-13', + 'iso885914' => 'ISO-8859-14', + 'iso885915' => 'ISO-8859-15', + 'iso88592' => 'ISO-8859-2', + 'iso88593' => 'ISO-8859-3', + 'iso88594' => 'ISO-8859-4', + 'iso88595' => 'ISO-8859-5', + 'iso88596' => 'ISO-8859-6', + 'iso88597' => 'ISO-8859-7', + 'iso88598' => 'ISO-8859-8', + 'iso88599' => 'ISO-8859-9', + 'iso_8859-1' => 'ISO-8859-1', + 'iso_8859-15' => 'ISO-8859-15', + 'iso_8859-1:1987' => 'ISO-8859-1', + 'iso_8859-2' => 'ISO-8859-2', + 'iso_8859-2:1987' => 'ISO-8859-2', + 'iso_8859-3' => 'ISO-8859-3', + 'iso_8859-3:1988' => 'ISO-8859-3', + 'iso_8859-4' => 'ISO-8859-4', + 'iso_8859-4:1988' => 'ISO-8859-4', + 'iso_8859-5' => 'ISO-8859-5', + 'iso_8859-5:1988' => 'ISO-8859-5', + 'iso_8859-6' => 'ISO-8859-6', + 'iso_8859-6:1987' => 'ISO-8859-6', + 'iso_8859-7' => 'ISO-8859-7', + 'iso_8859-7:1987' => 'ISO-8859-7', + 'iso_8859-8' => 'ISO-8859-8', + 'iso_8859-8:1988' => 'ISO-8859-8', + 'iso_8859-9' => 'ISO-8859-9', + 'iso_8859-9:1989' => 'ISO-8859-9', + 'koi' => 'KOI8-R', + 'koi8' => 'KOI8-R', + 'koi8-ru' => 'KOI8-U', + 'koi8_r' => 'KOI8-R', + 'korean' => 'EUC-KR', + 'ks_c_5601-1987' => 'EUC-KR', + 'ks_c_5601-1989' => 'EUC-KR', + 'ksc5601' => 'EUC-KR', + 'ksc_5601' => 'EUC-KR', + 'l1' => 'ISO-8859-1', + 'l2' => 'ISO-8859-2', + 'l3' => 'ISO-8859-3', + 'l4' => 'ISO-8859-4', + 'l5' => 'ISO-8859-9', + 'l6' => 'ISO-8859-10', + 'l9' => 'ISO-8859-15', + 'latin1' => 'ISO-8859-1', + 'latin2' => 'ISO-8859-2', + 'latin3' => 'ISO-8859-3', + 'latin4' => 'ISO-8859-4', + 'latin5' => 'ISO-8859-9', + 'latin6' => 'ISO-8859-10', + 'logical' => 'ISO-8859-8-I', + 'mac' => 'x-mac-roman', + 'macintosh' => 'x-mac-roman', + 'ms932' => 'Shift_JIS', + 'ms_kanji' => 'Shift_JIS', + 'shift-jis' => 'Shift_JIS', + 'sjis' => 'Shift_JIS', + 'sun_eu_greek' => 'ISO-8859-7', + 't.61' => 'T.61-8bit', + 'tis620' => 'TIS-620', + 'unicode-1-1-utf-7' => 'UTF-7', + 'unicode-1-1-utf-8' => 'UTF-8', + 'unicode-2-0-utf-7' => 'UTF-7', + 'visual' => 'ISO-8859-8', + 'windows-31j' => 'Shift_JIS', + 'windows-949' => 'EUC-KR', + 'x-cp1250' => 'windows-1250', + 'x-cp1251' => 'windows-1251', + 'x-cp1252' => 'windows-1252', + 'x-cp1253' => 'windows-1253', + 'x-cp1254' => 'windows-1254', + 'x-cp1255' => 'windows-1255', + 'x-cp1256' => 'windows-1256', + 'x-cp1257' => 'windows-1257', + 'x-cp1258' => 'windows-1258', + 'x-euc-jp' => 'EUC-JP', + 'x-gbk' => 'gbk', + 'x-iso-10646-ucs-2-be' => 'UTF-16BE', + 'x-iso-10646-ucs-2-le' => 'UTF-16LE', + 'x-iso-10646-ucs-4-be' => 'UTF-32BE', + 'x-iso-10646-ucs-4-le' => 'UTF-32LE', + 'x-sjis' => 'Shift_JIS', + 'x-unicode-2-0-utf-7' => 'UTF-7', + 'x-x-big5' => 'Big5', + 'zh_cn.euc' => 'GB2312', + 'zh_tw-big5' => 'Big5', + 'zh_tw-euc' => 'x-euc-tw', ]; /** @@ -272,23 +272,23 @@ final class Transcoder { static $utf8Aliases = [ 'unicode-1-1-utf-8' => true, - 'utf8' => true, - 'utf-8' => true, - 'UTF8' => true, - 'UTF-8' => true, + 'utf8' => true, + 'utf-8' => true, + 'UTF8' => true, + 'UTF-8' => true, ]; if (isset($utf8Aliases[$fromCharset])) { return $text; } - $originalFromCharset = $fromCharset; + $originalFromCharset = $fromCharset; $lowercaseFromCharset = \strtolower($fromCharset); if (isset(self::$charsetAliases[$lowercaseFromCharset])) { $fromCharset = self::$charsetAliases[$lowercaseFromCharset]; } - \set_error_handler(function () {}); + \set_error_handler(static function () {}); $iconvDecodedText = \iconv($fromCharset, 'UTF-8', $text); if (false === $iconvDecodedText) { @@ -302,8 +302,8 @@ final class Transcoder } $errorMessage = null; - $errorNumber = 0; - \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber) { $errorMessage = $message; $errorNumber = $nr; }); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php index d14ce716..ddf245b7 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php @@ -100,6 +100,11 @@ interface MessageInterface extends Message\BasicMessageInterface */ public function delete(): void; + /** + * Undelete message. + */ + public function undelete(): void; + /** * Set Flag Message. * diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php index 0e925954..04269bef 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php @@ -32,7 +32,7 @@ abstract class AbstractDate implements ConditionInterface */ public function __construct(DateTimeInterface $date, string $dateFormat = 'j-M-Y') { - $this->date = $date; + $this->date = $date; $this->dateFormat = $dateFormat; } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php index 7d522257..666a3d76 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/LogicalOperator/OrConditions.php @@ -43,7 +43,7 @@ final class OrConditions implements ConditionInterface */ public function toString(): string { - $conditions = \array_map(function (ConditionInterface $condition) { + $conditions = \array_map(static function (ConditionInterface $condition) { return $condition->toString(); }, $this->conditions); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php b/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php index d58d658f..0facdbfb 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/SearchExpression.php @@ -39,7 +39,7 @@ final class SearchExpression implements ConditionInterface */ public function toString(): string { - $conditions = \array_map(function (ConditionInterface $condition) { + $conditions = \array_map(static function (ConditionInterface $condition) { return $condition->toString(); }, $this->conditions); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php index 361ca2aa..0afdd66b 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Ddeboer\Imap; use Ddeboer\Imap\Exception\AuthenticationFailedException; +use Ddeboer\Imap\Exception\ResourceCheckFailureException; /** * An IMAP server. @@ -64,12 +65,12 @@ final class Server implements ServerInterface throw new \RuntimeException('IMAP extension must be enabled'); } - $this->hostname = $hostname; - $this->port = $port; - $this->flags = $flags ? '/' . \ltrim($flags, '/') : ''; + $this->hostname = $hostname; + $this->port = $port; + $this->flags = '' !== $flags ? '/' . \ltrim($flags, '/') : ''; $this->parameters = $parameters; - $this->options = $options; - $this->retries = $retries; + $this->options = $options; + $this->retries = $retries; } /** @@ -85,8 +86,8 @@ final class Server implements ServerInterface public function authenticate(string $username, string $password): ConnectionInterface { $errorMessage = null; - $errorNumber = 0; - \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorNumber = 0; + \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber) { $errorMessage = $message; $errorNumber = $nr; }); @@ -111,8 +112,17 @@ final class Server implements ServerInterface } $check = \imap_check($resource); - $mailbox = $check->Mailbox; - $connection = \substr($mailbox, 0, \strpos($mailbox, '}') + 1); + + if (false === $check) { + throw new ResourceCheckFailureException('Resource check failure'); + } + + $mailbox = $check->Mailbox; + $connection = $mailbox; + $curlyPosition = \strpos($mailbox, '}'); + if (false !== $curlyPosition) { + $connection = \substr($mailbox, 0, $curlyPosition + 1); + } // These are necessary to get rid of PHP throwing IMAP errors \imap_errors(); diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md index acd661e6..68f997ae 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md @@ -21,11 +21,12 @@ This extension can be used to... Yes. All known issues have been reproduced, fixed and tested. -We use Travis CI to help ensure code quality. You can see real-time statistics below: +We use GitHub Actions, Codecov, Codacy to help ensure code quality. You can see real-time statistics below: + +[![Actions Status](https://wdp9fww0r9.execute-api.us-west-2.amazonaws.com/production/badge/php-mime-mail-parser/php-mime-mail-parser?style=flat-square)](https://wdp9fww0r9.execute-api.us-west-2.amazonaws.com/production/results/php-mime-mail-parser/php-mime-mail-parser) +[![Coverage](https://img.shields.io/codecov/c/gh/php-mime-mail-parser/php-mime-mail-parser?style=flat-square)](https://codecov.io/gh/php-mime-mail-parser/php-mime-mail-parser) +[![Code Quality](https://img.shields.io/codacy/grade/4e0e44fee21147ddbdd18ff976251875?style=flat-square)](https://app.codacy.com/app/php-mime-mail-parser/php-mime-mail-parser) -[![Build Status](https://img.shields.io/travis/php-mime-mail-parser/php-mime-mail-parser/master.svg?style=flat-square)](https://travis-ci.com/php-mime-mail-parser/php-mime-mail-parser) -[![Coverage](https://img.shields.io/coveralls/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://coveralls.io/r/php-mime-mail-parser/php-mime-mail-parser) -[![Quality Score](https://img.shields.io/scrutinizer/g/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-mime-mail-parser/php-mime-mail-parser) ## How do I install it? diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh new file mode 100755 index 00000000..8505077f --- /dev/null +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +git clone https://github.com/php/pecl-mail-mailparse.git +cd pecl-mail-mailparse +phpize +./configure +sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c +make +sudo mv modules/mailparse.so /home/travis/.phpenv/versions/7.3.2/lib/php/extensions/no-debug-zts-20180731/ +echo 'extension=mailparse.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php index 091eba6a..1a731635 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php @@ -2,6 +2,8 @@ namespace PhpMimeMailParser; +use function var_dump; + /** * Attachment of php-mime-mail-parser * @@ -237,7 +239,9 @@ class Attachment // Determine filename switch ($filenameStrategy) { case Parser::ATTACHMENT_RANDOM_FILENAME: - $attachment_path = tempnam($attach_dir, ''); + $fileInfo = pathinfo($this->getFilename()); + $extension = empty($fileInfo['extension']) ? '' : '.'.$fileInfo['extension']; + $attachment_path = $attach_dir.uniqid().$extension; break; case Parser::ATTACHMENT_DUPLICATE_THROW: case Parser::ATTACHMENT_DUPLICATE_SUFFIX: diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php index 9768edea..cd219f22 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php @@ -326,7 +326,7 @@ class Charset implements CharsetManager return mb_convert_encoding($encodedString, 'utf-8', 'iso-2022-jp-ms'); } - if (array_search($charset, array_map('strtolower', mb_list_encodings()))) { + if (array_search($charset, $this->getSupportedEncodings())) { return mb_convert_encoding($encodedString, 'utf-8', $charset); } } @@ -347,4 +347,24 @@ class Charset implements CharsetManager return 'us-ascii'; } + + private function getSupportedEncodings() + { + return + array_map( + 'strtolower', + array_unique( + array_merge( + $enc = mb_list_encodings(), + call_user_func_array( + 'array_merge', + array_map( + "mb_encoding_aliases", + $enc + ) + ) + ) + ) + ); + } } diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php index a42728fb..a7b3cb0d 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php @@ -84,9 +84,9 @@ class MimePart implements \ArrayAccess { if (is_null($offset)) { $this->part[] = $value; - } else { - $this->part[$offset] = $value; + return; } + $this->part[$offset] = $value; } /** diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php index 8311663a..6502b57e 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php @@ -268,7 +268,7 @@ class Parser { if (isset($this->parts[1])) { $headers = $this->getPart('headers', $this->parts[1]); - foreach ($headers as $name => &$value) { + foreach ($headers as &$value) { if (is_array($value)) { foreach ($value as &$v) { $v = $this->decodeSingleHeader($v); @@ -514,20 +514,17 @@ class Parser if (isset($part['disposition-filename'])) { $filename = $this->decodeHeader($part['disposition-filename']); - // Escape all potentially unsafe characters from the filename - $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename); } elseif (isset($part['content-name'])) { // if we have no disposition but we have a content-name, it's a valid attachment. // we simulate the presence of an attachment disposition with a disposition filename $filename = $this->decodeHeader($part['content-name']); - // Escape all potentially unsafe characters from the filename - $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename); $disposition = 'attachment'; } elseif (in_array($part['content-type'], $non_attachment_types, true) && $disposition !== 'attachment') { // it is a message body, no attachment continue; - } elseif (substr($part['content-type'], 0, 10) !== 'multipart/') { + } elseif (substr($part['content-type'], 0, 10) !== 'multipart/' + && $part['content-type'] !== 'text/plain; (error)') { // if we cannot get it by getMessageBody(), we assume it is an attachment $disposition = 'attachment'; } @@ -539,6 +536,9 @@ class Parser if ($filename == 'noname') { $nonameIter++; $filename = 'noname'.$nonameIter; + } else { + // Escape all potentially unsafe characters from the filename + $filename = preg_replace('((^\.)|\/|[\n|\r|\n\r]|(\.$))', '_', $filename); } $headersAttachments = $this->getPart('headers', $part); diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md index 595e1df5..073f464a 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md @@ -11,7 +11,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi ## Requirements * Tested on PHP 5.4 up to 7.2 -* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider. +* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `ImageChartsQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider. * [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG. ## Installation @@ -64,11 +64,11 @@ The `createSecret()` method accepts two arguments: `$bits` (default: `80`) and ` Another, more user-friendly, way to get the shared secret into the app is to generate a [QR-code](http://en.wikipedia.org/wiki/QR_code) which can be scanned by the app. To generate these QR codes you can use any one of the built-in `QRProvider` classes: -1. `GoogleQRCodeProvider` (default) +1. `ImageChartsQRCodeProvider` (default) 2. `QRServerProvider` 3. `QRicketProvider` -...or implement your own provider. To implement your own provider all you need to do is implement the `IQRCodeProvider` interface. You can use the built-in providers mentioned before to serve as an example or read the next chapter in this file. The built-in classes all use a 3rd (e.g. external) party (Google, QRServer and QRicket) for the hard work of generating QR-codes (note: each of these services might at some point not be available or impose limitations to the number of codes generated per day, hour etc.). You could, however, easily use a project like [PHP QR Code](http://phpqrcode.sourceforge.net/) (or one of the [many others](https://packagist.org/search/?q=qr)) to generate your QR-codes without depending on external sources. Later on we'll [demonstrate](#qr-code-providers) how to do this. +...or implement your own provider. To implement your own provider all you need to do is implement the `IQRCodeProvider` interface. You can use the built-in providers mentioned before to serve as an example or read the next chapter in this file. The built-in classes all use a 3rd (e.g. external) party (Image-charts, QRServer and QRicket) for the hard work of generating QR-codes (note: each of these services might at some point not be available or impose limitations to the number of codes generated per day, hour etc.). You could, however, easily use a project like [PHP QR Code](http://phpqrcode.sourceforge.net/) (or one of the [many others](https://packagist.org/search/?q=qr)) to generate your QR-codes without depending on external sources. Later on we'll [demonstrate](#qr-code-providers) how to do this. The built-in providers all have some provider-specific 'tweaks' you can 'apply'. Some provide support for different colors, others may let you specify the desired image-format etc. What they all have in common is that they return a QR-code as binary blob which, in turn, will be turned into a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) by the `TwoFactorAuth` class. This makes it easy for you to display the image without requiring extra 'roundtrips' from browser to server and vice versa. @@ -121,7 +121,7 @@ public function verifyCode($secret, $code, $discrepancy = 1, $time = null): bool As mentioned before, this library comes with three 'built-in' QR-code providers. This chapter will touch the subject a bit but most of it should be self-explanatory. The `TwoFactorAuth`-class accepts a `$qrcodeprovider` argument which lets you specify a built-in or custom QR-code provider. All three built-in providers do a simple HTTP request to retrieve an image using cURL and implement the [`IQRCodeProvider`](lib/Providers/Qr/IQRCodeProvider.php) interface which is all you need to implement to write your own QR-code provider. -The default provider is the [`GoogleQRCodeProvider`](lib/Providers/Qr/GoogleQRCodeProvider.php) which uses the [Google Chart Tools](https://developers.google.com/chart/infographics/docs/qr_codes) to render QR-codes. Then we have the [`QRServerProvider`](lib/Providers/Qr/QRServerProvider.php) which uses the [goqr.me API](http://goqr.me/api/doc/create-qr-code/) and finally we have the [`QRicketProvider`](lib/Providers/Qr/QRicketProvider.php) which uses the [QRickit API](http://qrickit.com/qrickit_apps/qrickit_api.php). All three inherit from a common (abstract) baseclass named [`BaseHTTPQRCodeProvider`](lib/Providers/Qr/BaseHTTPQRCodeProvider.php) because all three share the same functionality: retrieve an image from a 3rd party over HTTP. All three classes have constructors that allow you to tweak some settings and most, if not all, arguments should speak for themselves. If you're not sure which values are supported, click the links in this paragraph for documentation on the API's that are utilized by these classes. +The default provider is the [`ImageChartsQRCodeProvider`](lib/Providers/Qr/ImageChartsQRCodeProvider.php) which uses the [image-charts.com replacement for Google Image Charts](https://image-charts.com) to render QR-codes. Then we have the [`QRServerProvider`](lib/Providers/Qr/QRServerProvider.php) which uses the [goqr.me API](http://goqr.me/api/doc/create-qr-code/) and finally we have the [`QRicketProvider`](lib/Providers/Qr/QRicketProvider.php) which uses the [QRickit API](http://qrickit.com/qrickit_apps/qrickit_api.php). All three inherit from a common (abstract) baseclass named [`BaseHTTPQRCodeProvider`](lib/Providers/Qr/BaseHTTPQRCodeProvider.php) because all three share the same functionality: retrieve an image from a 3rd party over HTTP. All three classes have constructors that allow you to tweak some settings and most, if not all, arguments should speak for themselves. If you're not sure which values are supported, click the links in this paragraph for documentation on the API's that are utilized by these classes. If you don't like any of the built-in classes because you don't want to rely on external resources for example or because you're paranoid about sending the TOTP secret to these 3rd parties (which is useless to them since they miss *at least one* other factor in the [MFA process](http://en.wikipedia.org/wiki/Multi-factor_authentication)), feel tree to implement your own. The `IQRCodeProvider` interface couldn't be any simpler. All you need to do is implement 2 methods: @@ -186,7 +186,7 @@ Another set of providers in this library are the Time Providers; this library pr You can easily implement your own `TimeProvider` by simply implementing the `ITimeProvider` interface. -As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to Google's and convert-unix-time.com's current time. You can pass an array of `ITimeProvider`s and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `NTPTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly. +As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to the default `NTPTimeProvider` and `HttpTimeProvider`. You can pass an array of `ITimeProvider`s to change this and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `NTPTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly. ## Integrations diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/TwoFactorAuth.phpproj b/data/web/inc/lib/vendor/robthree/twofactorauth/TwoFactorAuth.phpproj index 995e964e..b7e00d60 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/TwoFactorAuth.phpproj +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/TwoFactorAuth.phpproj @@ -27,7 +27,7 @@ - + diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json index 0bbf8b35..cb3b2b1f 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json @@ -1,7 +1,7 @@ { "name": "robthree/twofactorauth", "description": "Two Factor Authentication", - "version": "1.6.5", + "version": "1.6.7", "type": "library", "keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ], "homepage": "https://github.com/RobThree/TwoFactorAuth", diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/GoogleQRCodeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php similarity index 78% rename from data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/GoogleQRCodeProvider.php rename to data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php index 19e086b7..cc094c31 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/GoogleQRCodeProvider.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/ImageChartsQRCodeProvider.php @@ -2,8 +2,8 @@ namespace RobThree\Auth\Providers\Qr; -// https://developers.google.com/chart/infographics/docs/qr_codes -class GoogleQRCodeProvider extends BaseHTTPQRCodeProvider +// https://image-charts.com +class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider { public $errorcorrectionlevel; public $margin; @@ -31,8 +31,8 @@ class GoogleQRCodeProvider extends BaseHTTPQRCodeProvider public function getUrl($qrtext, $size) { - return 'https://chart.googleapis.com/chart?cht=qr' - . '&chs=' . $size . 'x' . $size + return 'https://image-charts.com/chart?cht=qr' + . '&chs=' . ceil($size/2) . 'x' . ceil($size/2) . '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin . '&chl=' . rawurlencode($qrtext); } diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php index 3e615030..7bc067d1 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php @@ -212,7 +212,7 @@ class TwoFactorAuth { // Set default QR Code provider if none was specified if (null === $this->qrcodeprovider) { - return $this->qrcodeprovider = new Providers\Qr\GoogleQRCodeProvider(); + return $this->qrcodeprovider = new Providers\Qr\QRServerProvider(); } return $this->qrcodeprovider; } diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php b/data/web/inc/lib/vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php index accfd5d7..a0f2f673 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/tests/TwoFactorAuthTest.php @@ -4,7 +4,7 @@ require_once 'lib/TwoFactorAuthException.php'; require_once 'lib/Providers/Qr/IQRCodeProvider.php'; require_once 'lib/Providers/Qr/BaseHTTPQRCodeProvider.php'; -require_once 'lib/Providers/Qr/GoogleQRCodeProvider.php'; +require_once 'lib/Providers/Qr/ImageChartsQRCodeProvider.php'; require_once 'lib/Providers/Qr/QRException.php'; require_once 'lib/Providers/Rng/IRNGProvider.php'; diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 556c895b..35688315 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -86,6 +86,33 @@ if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) { exit; } +// OAuth2 +class mailcowPdo extends OAuth2\Storage\Pdo { + public function __construct($connection, $config = array()) { + parent::__construct($connection, $config); + $this->config['user_table'] = 'mailbox'; + } + public function checkUserCredentials($username, $password) { + if (check_login($username, $password) == 'user') { + return true; + } + return false; + } + public function getUserDetails($username) { + return $this->getUser($username); + } +} +$oauth2_scope_storage = new OAuth2\Storage\Memory(array('default_scope' => 'profile', 'supported_scopes' => array('profile'))); +$oauth2_storage = new mailcowPdo(array('dsn' => $dsn, 'username' => $database_user, 'password' => $database_pass)); +$oauth2_server = new OAuth2\Server($oauth2_storage, array( + 'always_issue_new_refresh_token' => true, + 'refresh_token_lifetime' => 2678400, +)); +$oauth2_server->setScopeUtil(new OAuth2\Scope($oauth2_scope_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\AuthorizationCode($oauth2_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\UserCredentials($oauth2_storage)); +$oauth2_server->addGrantType(new OAuth2\GrantType\RefreshToken($oauth2_storage)); + function exception_handler($e) { if ($e instanceof PDOException) { $_SESSION['return'][] = array( @@ -173,6 +200,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php'; diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index 1f671b3d..22c07caa 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -633,7 +633,8 @@ $lang['admin']['forwarding_hosts'] = 'Weiterleitungs-Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.'; $lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.'; $lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.
- Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.'; + Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.
+ Gilt neben ausgewählter Domain auch für untergeordnete Alias-Domains.'; $lang['admin']['transports_hint'] = '→ Transport Maps überwiegen senderabhängige Transport Maps.
→ Transport Maps ignorieren Mailbox-Einstellungen für ausgehende Verschlüsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.
→ Der Transport erfolgt immer via "smtp:".
@@ -710,6 +711,7 @@ $lang['admin']['help_text'] = "Hilfstext unter Login-Maske (HTML zulässig)"; $lang['admin']['title_name'] = '"mailcow UI" Webseiten Titel'; $lang['admin']['main_name'] = '"mailcow UI" Name'; $lang['admin']['apps_name'] = '"mailcow Apps" Name'; +$lang['admin']['ui_impress'] = 'Impressum, Footer (HTML zulässig)'; $lang['admin']['customize'] = "UI Anpassung"; $lang['admin']['change_logo'] = "Logo ändern"; @@ -875,7 +877,7 @@ $lang['admin']['customer_id'] = 'Kunde'; $lang['admin']['service_id'] = 'Service'; $lang['admin']['lookup_mx'] = 'Ziel gegen MX prüfen (etwa .outlook.com, um alle Ziele mit MX *.outlook.com zu routen)'; -$lang['edit']['mbox_rl_info'] = 'Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Eub Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.'; +$lang['edit']['mbox_rl_info'] = 'Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.'; $lang['add']['relayhost_wrapped_tls_info'] = 'Bitte keine TLS-wrapped Ports verwenden (etwa SMTPS via Port 465/tcp).
Der Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter "TLS Policy Maps" erzwungen werden.'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index ec0d2019..795e531c 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -657,7 +657,8 @@ $lang['admin']['forwarding_hosts'] = 'Forwarding Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.'; $lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).'; $lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.
- The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.'; + The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.
+ Affects selected domains including alias domains.'; $lang['admin']['transports_hint'] = '→ A transport map entry overrules a sender-dependent transport map.
→ Outbound TLS policy settings per-user are ignored and can only be enforced by TLS policy map entries.
→ The transport service for defined transports is always "smtp:".
@@ -726,6 +727,7 @@ $lang['admin']['help_text'] = "Override help text below login mask (HTML allowed $lang['admin']['title_name'] = '"mailcow UI" website title'; $lang['admin']['main_name'] = '"mailcow UI" name'; $lang['admin']['apps_name'] = '"mailcow Apps" name'; +$lang['admin']['ui_impress'] = 'Impress, Footer note (HTML allowed)'; $lang['admin']['customize'] = "Customize"; $lang['admin']['change_logo'] = "Change logo"; From 8893b8502c355c67ced111b73a92b3ebff7e2fb1 Mon Sep 17 00:00:00 2001 From: Geitenijs <40541903+Geitenijs@users.noreply.github.com> Date: Sat, 28 Sep 2019 20:55:41 +0200 Subject: [PATCH 7/7] Update lang.nl.php --- data/web/lang/lang.nl.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php index 33210f6b..85812380 100644 --- a/data/web/lang/lang.nl.php +++ b/data/web/lang/lang.nl.php @@ -642,7 +642,7 @@ $lang['admin']['in_use_by'] = 'In gebruik door'; $lang['admin']['forwarding_hosts'] = 'Doorstuurhosts'; $lang['admin']['forwarding_hosts_hint'] = 'Inkomende berichten worden onvoorwaardelijk geaccepteerd vanaf iedere host hieronder vermeld. Deze hosts worden hierdoor niet gecontroleerd op DNSBLs, en zullen de greylisting omzeilen. Spam wordt daarentegen zoals gebruikelijk in de spamfolder geplaatst. Dit wordt vaak gebruikt om mailservers te specificeren die mails doorsturen naar deze Mailcow-server.'; $lang['admin']['forwarding_hosts_add_hint'] = 'Het is mogelijk om IPv4- of IPv6-adressen, netwerken in CIDR-notatie, hostnames (worden omgezet naar IP-adressen) of domeinnamen (worden tevens omgezet naar IP-adressen of, bij gebrek daaraan, MX-records) op te geven.'; -$lang['admin']['relayhosts_hint'] = 'Stel afzender-afhankelijke transportkaarten in om deze te kunnen gebruiken bij de configuratie van een domein.
De transportservice is altijd "smtp:". Er wordt rekening gehouden met het uitgaande versleutelingsbeleid van individuele gebruikers.'; +$lang['admin']['relayhosts_hint'] = 'Stel afzender-afhankelijke transportkaarten in om deze te kunnen gebruiken bij de configuratie van een domein.
De transportservice is altijd "smtp:". Er wordt rekening gehouden met het uitgaande versleutelingsbeleid van individuele gebruikers.
Beïnvloedt geselecteerde domeinen, inclusief bijbehorende aliasdomeinen.'; $lang['admin']['transports_hint'] = '→ Een transportkaart wordt boven een afzender-afhankelijke transportkaart verkozen.
→ Het uitgaande versleutelingsbeleid van individuele gebruikers wordt genegeerd en kan enkel worden gehandhaafd doormiddel van globaal versleutelingsbeleid.
→ De transportservice is altijd "smtp:".
@@ -706,10 +706,11 @@ $lang['admin']['quota_notification_sender'] = "Afzender van notificaties"; $lang['admin']['quota_notification_subject'] = "Onderwerp van notificaties"; $lang['admin']['quota_notification_html'] = "Notificatiesjabloon:
Laat leeg om de standaardsjabloon te herstellen."; $lang['admin']['ui_texts'] = "Labels en teksten"; -$lang['admin']['help_text'] = "Pas hulpteksten onder inlogvenster aan (HTML toegestaan)"; +$lang['admin']['help_text'] = "Hulpteksten onder inlogvenster (HTML toegestaan)"; $lang['admin']['title_name'] = '"Mailcow" (website-titel)'; $lang['admin']['main_name'] = '"Mailcow"'; $lang['admin']['apps_name'] = '"Mailcow-apps"'; +$lang['admin']['ui_impress'] = 'Footer-vermelding (HTML toegestaan)'; $lang['admin']['customize'] = "Uiterlijk"; $lang['admin']['change_logo'] = "Logo";